

<!doctype html>
<html lang="en">
  <head>
    
    
    <link href="https://files.realpython.com" rel="preconnect">

    <title>Build a Python MCP Client to Test Servers From Your Terminal – Real Python</title>
    <meta name="author" content="Real Python">
    <meta name="description" content="Follow this Python project to build an MCP client that discovers MCP server capabilities and feeds an AI-powered chat with tool calls.">
    <meta name="keywords" content="">

    
  


    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover">

    
      <link rel="stylesheet" href="/static/realpython.min.fe9eea5e59ad.css">
      
      <link rel="stylesheet" href="/static/gfonts/font.5ac42994de49.css">
      <link rel="preload" href="/static/glightbox.min.9b438b29cef1.css" as="style" onload="this.onload=null;this.rel='stylesheet'"><noscript><link rel="stylesheet" href="/static/glightbox.min.9b438b29cef1.css"></noscript>
      
    

    
  
  <link rel="preload" as="image" href="https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg" imagesrcset="/cdn-cgi/image/width=480,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 480w, /cdn-cgi/image/width=640,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 640w, /cdn-cgi/image/width=960,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 960w, /cdn-cgi/image/width=1920,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 1920w" imagesizes="(min-width: 1200px) 690px, (min-width: 780px) calc(-5vw + 669px), (min-width: 580px) 510px, calc(100vw - 30px)">


    
    
      <link rel="canonical" href="https://realpython.com/python-mcp-client/">

      
      
        <meta name="twitter:card" content="summary_large_image">
        <meta name="twitter:image" content="https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg">
        <meta property="og:image" content="https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg">
        <meta name="robots" content="max-image-preview:large">
      
      <meta name="twitter:creator" content="@realpython">
      <meta name="twitter:site" content="@realpython">
      <meta property="og:title" content="Build a Python MCP Client to Test Servers From Your Terminal – Real Python">
      <meta property="og:type" content="article">
      <meta property="og:url" content="https://realpython.com/python-mcp-client/">
      <meta property="og:description" content="Follow this Python project to build an MCP client that discovers MCP server capabilities and feeds an AI-powered chat with tool calls.">
    

    <link href="/static/favicon.68cbf4197b0c.png" rel="icon">
    <link href="https://realpython.com/atom.xml" rel="alternate" title="Real Python" type="application/atom+xml">
    <link rel="manifest" href="/manifest.json">

    

    
    

    
    <script id="icons-data" type="application/json">{"iconsUrl": "/static/icons.0fae35ff985f.svg"}</script>
    <script id="optins-data" type="application/json">{"turnstileSiteKey": "0x4AAAAAAAQQUCLRXBi8NnUh"}</script>

    
    
<script async src="https://www.googletagmanager.com/gtag/js?id=G-L7L6L5F6Y6"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-L7L6L5F6Y6',{'content_group': 'article'});

  gtag('get', 'G-L7L6L5F6Y6', 'client_id', (clid) => {
    document.cookie = 'gaclid=' + clid + ';max-age=86400000;path=/;secure';
  });
  
</script>

  </head>
  <body data-authenticated="false">
    
      

<nav class="navbar fixed-top navbar-expand-lg navbar-dark flex-column nav-message user-select-none">
  <div class="container flex-row">

    <a class="navbar-brand" href="/">
      <img src="/static/real-python-logo.893c30edea53.svg" width="165" height="40" class="d-inline-block align-top" alt="Real Python">
    </a>

    
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>

    <div class="collapse navbar-collapse navbar-nav-scroll" id="navbarSupportedContent" role="navigation" aria-label="Main Navigation">

      <ul class="navbar-nav mr-2">

        <li class="nav-item">
          <a class="nav-link rounded" href="/start-here/">Start&nbsp;Here</a>
        </li>

        <li class="nav-item dropdown">
          <a class="nav-link rounded dropdown-toggle" href="#" id="navbarDropdownLibrary" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
            <span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#brands--python"/></svg></span> Learn Python
          </a>
          <div class="dropdown-menu" aria-labelledby="navbarDropdownLibrary">
            
<a class="dropdown-item " href="/search?kind=article&kind=course&order=newest" style="color: #ff7e73; line-height: 110%;"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--graduation-cap"/></svg></span>Python Tutorials&nbsp;→<br><small class="text-secondary">In-depth articles and video courses</small></a>


            
<a class="dropdown-item " href="/learning-paths/" style="color: #ffc873; line-height: 110%;"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@learning-path"/></svg></span>Learning Paths&nbsp;→<br><small class="text-secondary">Guided study plans for accelerated learning</small></a>


            
<a class="dropdown-item " href="/quizzes/" style="color: #abe0e5; line-height: 110%;"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@quiz"/></svg></span>Quizzes & Exercises&nbsp;→<br><small class="text-secondary">Check your learning progress</small></a>


            
<a class="dropdown-item " href="/tutorials/all/" style="color: #ccc; line-height: 110%;"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@category"/></svg></span>Browse Topics&nbsp;→<br><small class="text-secondary">Focus on a specific area or skill level</small></a>


            
<a class="dropdown-item " href="/community/" style="color: #e5c6ab; line-height: 110%;"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--message-smile"/></svg></span>Community Chat&nbsp;→<br><small class="text-secondary">Learn with other Pythonistas</small></a>


            
<a class="dropdown-item " href="/office-hours/" style="color: #e5c6ab; line-height: 110%;"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--users"/></svg></span>Office Hours&nbsp;→<br><small class="text-secondary">Live Q&A calls with Python experts</small></a>


            
<a class="dropdown-item " href="/podcasts/rpp/" style="color: #b8abe5; line-height: 110%;"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@podcast"/></svg></span>Podcast&nbsp;→<br><small class="text-secondary">Hear what’s new in the world of Python</small></a>


            
<a class="dropdown-item " href="/products/books/" style="color: #abe5b1; line-height: 110%;"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--book-heart"/></svg></span>Books&nbsp;→<br><small class="text-secondary">Round out your knowledge and learn offline</small></a>


            
<a class="dropdown-item " href="/ref/" style="color: #abe0e5; line-height: 110%;"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@reference"/></svg></span>Reference&nbsp;→<br><small class="text-secondary">Concise definitions for common Python terms</small></a>


            
<a class="dropdown-item " href="/mentor/" style="color: #ffc873; line-height: 110%;"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@code-mentor"/></svg></span>Code Mentor&nbsp;→<span class="float-right badge badge-pill badge-indigo">Beta</span><br><small class="text-secondary">Personalized code assistance &amp; learning tools</small></a>


            
              
              
<a class="dropdown-item border-top" href="/account/join/" style="color: #ffc107; line-height: 110%;"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--star"/></svg></span>Unlock All Content&nbsp;→</a>

              
            
          </div>
        </li>

        <li class="nav-item dropdown">
          <a class="nav-link rounded dropdown-toggle" href="#" id="navbarDropdownMore" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
            More
          </a>
          <div class="dropdown-menu" aria-labelledby="navbarDropdownMore">
            <a class="dropdown-item" href="/learner-stories/">Learner Stories</a>
            <a class="dropdown-item" href="/newsletter/">Python Newsletter</a>
            <a class="dropdown-item" href="https://www.pythonjobshq.com" target="_blank">Python Job Board</a>
            <a class="dropdown-item" href="/team/">Meet the Team</a>
            <a class="dropdown-item" href="/write-for-us/">Become a Tutorial Writer</a>
            <a class="dropdown-item" href="/become-an-instructor/">Become a Video Instructor</a>
          </div>
        </li>

      </ul> 

      
      <div class="d-block d-lg-none"> 
        <ul class="navbar-nav">
          <li class="nav-item">
            <a class="nav-link rounded" href="/search" title="Search"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#v4--search"/></svg></span> Search</a>
          </li>
        </ul>
      </div>
      
      <div class="d-none d-lg-flex align-items-center mr-2 flex-fill">
        <form class="form-inline w-100 position-relative" action="/search" method="GET">
          <a class="js-search-form-submit position-absolute" href="/search" title="Search" aria-hidden="true" tabindex="-1"><span class="icon baseline text-muted pl-2" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#v4--search"/></svg></span></a>
          <input class="search-autocomplete search-field form-control form-control-md mr-sm-1 mr-lg-2 w-100" style="padding-left: 2rem;" maxlength=50 type="search" placeholder="Search" aria-label="Search" aria-keyshortcuts="/ Control+J Meta+J" name="q" autocomplete="off">
          <span class="search-hotkey-indicator position-absolute px-2 border border-input text-input rounded small user-select-none" style="right: 1em;" title="Press / (forward slash) or Ctrl+J to open search" aria-hidden="true">/</span>
          <input type="hidden" name="_from" value="nav">
        </form>
      </div>
      

      <ul class="navbar-nav ml-auto">
        
          <li class="nav-item form-inline mr-2 my-2 ml-3 ml-lg-0">
            <a class="btn btn-primary px-5 px-lg-3" href="/account/join/">Join</a>
          </li>
        

        
          <li class="nav-item">
            <a class="nav-link rounded mx-0 px-lg-2" href="/account/login/?next=%2Fpython-mcp-client%2F">Sign&#8209;In</a>
          </li>
        
      </ul>

    
    </div>

  </div>

  
    
  

</nav>

    

    <div class="container main-content">
      

      
  <div class="row justify-content-center">

    
    <aside class="col-md-7 col-lg-4 order-2 d-none d-lg-block">
      
  




<div class="card mb-3 bg-secondary">
  <form class="card-body" action="/optins/process/" method="post">
    <div class="form-group">
      <p class="h5 text-muted text-center">— FREE Email Series —</p>
      <p class="h3 text-center">🐍 Python Tricks 💌</p>
      <p><img loading="lazy" class="img-fluid rounded" src="/static/pytrick-dict-merge.4201a0125a5e.png" width="738" height="490" alt="Python Tricks Dictionary Merge"></p>
    </div>
    <div class="form-group">
      <input type="hidden" name="csrfmiddlewaretoken" value="4mTPzdUJcZ3q6Cza0MFKlaiFbn5VQMnrDmwPPTFgWLG5uBvkIMHgDS6MfDYjw832">
      <input type="hidden" name="slug" value="static-python-tricks-sidebar">
      <input type="hidden" name="source_path" value="/python-mcp-client/">
      <input type="email" class="form-control form-control-md" name="email" placeholder="Email&hellip;" required>
    </div>
    <button type="submit" name="submit" class="btn btn-primary btn-md btn-block">Get Python Tricks »</button>
    <p class="mb-0 mt-2 text-muted text-center">🔒 No spam. Unsubscribe any time.</p>
  </form>
</div>


  


<div class="sidebar-module sidebar-module-inset border">
  <a href="/tutorials/all/" class="badge badge-light text-muted"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@category"/></svg></span>Browse Topics</a>

  <a href="/learning-paths/" class="badge badge-light text-muted"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--map-location-dot"/></svg></span>Guided Learning Paths</a>

  <br>

  <a href="/search?level=basics" class="badge badge-light text-muted"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@level"/></svg></span> Basics</a>

  <a href="/search?level=intermediate" class="badge badge-light text-muted"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@level"/></svg></span><span class="icon baseline tight-left" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@level"/></svg></span> Intermediate</a>

  <a href="/search?level=advanced" class="badge badge-light text-muted"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@level"/></svg></span><span class="icon baseline tight-left" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@level"/></svg></span><span class="icon baseline tight-left" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@level"/></svg></span> Advanced</a>

  <hr class="my-2">

  
    
  
    
  
    
  
    
      
<a href="/tutorials/ai/" class="badge badge-light text-muted" data-previewable>ai</a>

    
  
    
      
<a href="/tutorials/algorithms/" class="badge badge-light text-muted" data-previewable>algorithms</a>

    
  
    
      
<a href="/tutorials/api/" class="badge badge-light text-muted" data-previewable>api</a>

    
  
    
      
<a href="/tutorials/best-practices/" class="badge badge-light text-muted" data-previewable>best-practices</a>

    
  
    
      
<a href="/tutorials/career/" class="badge badge-light text-muted" data-previewable>career</a>

    
  
    
      
<a href="/tutorials/community/" class="badge badge-light text-muted" data-previewable>community</a>

    
  
    
      
<a href="/tutorials/databases/" class="badge badge-light text-muted" data-previewable>databases</a>

    
  
    
      
<a href="/tutorials/data-science/" class="badge badge-light text-muted" data-previewable>data-science</a>

    
  
    
      
<a href="/tutorials/data-structures/" class="badge badge-light text-muted" data-previewable>data-structures</a>

    
  
    
      
<a href="/tutorials/data-viz/" class="badge badge-light text-muted" data-previewable>data-viz</a>

    
  
    
      
<a href="/tutorials/devops/" class="badge badge-light text-muted" data-previewable>devops</a>

    
  
    
      
<a href="/tutorials/django/" class="badge badge-light text-muted" data-previewable>django</a>

    
  
    
      
<a href="/tutorials/docker/" class="badge badge-light text-muted" data-previewable>docker</a>

    
  
    
      
<a href="/tutorials/editors/" class="badge badge-light text-muted" data-previewable>editors</a>

    
  
    
      
<a href="/tutorials/flask/" class="badge badge-light text-muted" data-previewable>flask</a>

    
  
    
      
<a href="/tutorials/front-end/" class="badge badge-light text-muted" data-previewable>front-end</a>

    
  
    
      
<a href="/tutorials/gamedev/" class="badge badge-light text-muted" data-previewable>gamedev</a>

    
  
    
      
<a href="/tutorials/gui/" class="badge badge-light text-muted" data-previewable>gui</a>

    
  
    
      
<a href="/tutorials/machine-learning/" class="badge badge-light text-muted" data-previewable>machine-learning</a>

    
  
    
      
<a href="/tutorials/news/" class="badge badge-light text-muted" data-previewable>news</a>

    
  
    
      
<a href="/tutorials/numpy/" class="badge badge-light text-muted" data-previewable>numpy</a>

    
  
    
      
<a href="/tutorials/projects/" class="badge badge-light text-muted" data-previewable>projects</a>

    
  
    
      
<a href="/tutorials/python/" class="badge badge-light text-muted" data-previewable>python</a>

    
  
    
      
<a href="/tutorials/stdlib/" class="badge badge-light text-muted" data-previewable>stdlib</a>

    
  
    
      
<a href="/tutorials/testing/" class="badge badge-light text-muted" data-previewable>testing</a>

    
  
    
      
<a href="/tutorials/tools/" class="badge badge-light text-muted" data-previewable>tools</a>

    
  
    
      
<a href="/tutorials/web-dev/" class="badge badge-light text-muted" data-previewable>web-dev</a>

    
  
    
      
<a href="/tutorials/web-scraping/" class="badge badge-light text-muted" data-previewable>web-scraping</a>

    
  
</div>



  
<div class="sidebar-module sidebar-module-inset p-0" style="overflow:hidden;">
  
<div style="display:block;position:relative;">
  <div style="display:block;width:100%;padding-top:100%;"></div>
  <div class="rpad" data-unit="1x1" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;"></div>
</div>


</div>



  <div class="sidebar-sticky sidebar-nav-message">
    
      


<table-of-contents>
  <div class="bg-light sidebar-module sidebar-module-inset text-wrap-pretty">
    <p class="h4 text-muted"><a class="link-unstyled" href="#toc">Table of Contents</a></p>
    <div class="toc">
<ul>
<li><a href="#prerequisites">Prerequisites</a></li>
<li><a href="#step-1-set-up-the-project-and-the-environment">Step 1: Set Up the Project and the Environment</a></li>
<li><a href="#step-2-write-a-minimal-mcp-client-with-the-python-sdk">Step 2: Write a Minimal MCP Client With the Python SDK</a></li>
<li><a href="#step-3-discover-server-tools-prompts-and-resources">Step 3: Discover Server Tools, Prompts, and Resources</a><ul>
<li><a href="#build-the-mcp-clients-cli-with-argparse">Build the MCP Client&rsquo;s CLI With argparse</a></li>
<li><a href="#add-the-members-command-line-option">Add the --members Command-Line Option</a></li>
<li><a href="#update-the-clients-entry-point-script">Update the Client&rsquo;s Entry-Point Script</a></li>
</ul>
</li>
<li><a href="#step-4-add-an-ai-powered-chat">Step 4: Add an AI-Powered Chat</a><ul>
<li><a href="#add-the-chat-command-line-option">Add the --chat Command-Line Option</a></li>
<li><a href="#create-a-chat-handler-with-openais-python-sdk">Create a Chat Handler With OpenAI&rsquo;s Python SDK</a></li>
<li><a href="#connect-the-chat-with-the-mcp-client">Connect the Chat With the MCP Client</a></li>
<li><a href="#update-the-clients-entry-point-script_1">Update the Client&rsquo;s Entry-Point Script</a></li>
<li><a href="#make-the-mcp-client-app-installable">Make the MCP Client App Installable</a></li>
</ul>
</li>
<li><a href="#conclusion">Conclusion</a></li>
<li><a href="#next-steps">Next Steps</a></li>
<li><a href="#frequently-asked-questions">Frequently Asked Questions</a></li>
</ul>
</div>

  </div>
</table-of-contents>

    

    <div class="sidebar-module sidebar-module-inset d-flex justify-content-center flex-wrap row-gap-1 col-gap-2 my-3 py-0">
      


<completion-status
  class="btn-group mb-0"
  article-slug="python-mcp-client"
  
  
  
  data-api-article-bookmark-url="/api/v1/articles/python-mcp-client/bookmark/"
  data-api-article-completion-status-url="/api/v1/articles/python-mcp-client/completion_status/">
  <button
    data-target-complete-button
    title="Click to mark as completed"
    class="btn btn-secondary border-right"
    style="border-top-right-radius: 0; border-bottom-right-radius: 0;"
    disabled>Mark as Completed</button>
  <button
    data-target-bookmark-button
    title="Add bookmark"
    class="btn btn-secondary border-left"
    disabled><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#light--bookmark"/></svg></span></button>
</completion-status>

      <div class="w-100"></div> 
      



<div class="btn-group mb-0">
  <a class="btn btn-secondary border-right" style="border-top-right-radius: 0; border-bottom-right-radius: 0;" title="Liked it" role="button" aria-label="Liked it (thumbs up)" href="/feedback/survey/article/python-mcp-client/liked/?from=article-sidebar" target="_blank"><span class="icon" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#light--thumbs-up"/></svg></span></a> 
  <a class="btn btn-secondary border-left" role="button" aria-label="Disliked it (thumbs down)" title="Disliked it" href="/feedback/survey/article/python-mcp-client/disliked/?from=article-sidebar" target="_blank"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#light--thumbs-down"/></svg></span></a>
</div>

      
        









<share-button
  url="https://realpython.com/python-mcp-client/"
  url-title="Build a Python MCP Client to Test Servers From Your Terminal"
  twitter-text="Interesting #Python article by @realpython:"
  bluesky-text="Interesting #Python article by @realpython.com:"
  email-subject="Python article for you"
  email-body="Check out this Python article:%0A%0ABuild a Python MCP Client to Test Servers From Your Terminal"
>
  <button class="btn btn-secondary" type="button" title="Share" aria-label="Share"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@share-light"/></svg></span>Share</button>
</share-button>


      
    </div>

    

    
      <div class="sidebar-module sidebar-module-inset p-0" style="overflow:hidden;">
        
<div style="display:block;position:relative;">
  <div style="display:block;width:100%;padding-top:25%;"></div>
  <div class="rpad" data-unit="4x1" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;"></div>
</div>


      </div>
    

    
  </div>


    </aside>

    <div class="col-md-11 col-lg-8 article with-headerlinks">
      

  
    <figure class="embed-responsive embed-responsive-16by9 rounded">
      
        <img fetchpriority="high" class="card-img-top m-0 p-0 embed-responsive-item" style="object-fit: contain; background: #ffc973;" alt="Build a Python MCP Client to Test Servers From Your Terminal" width="1920" height="1080" src="https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg" srcset="/cdn-cgi/image/width=480,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 480w, /cdn-cgi/image/width=640,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 640w, /cdn-cgi/image/width=960,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 960w, /cdn-cgi/image/width=1920,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 1920w" sizes="(min-width: 1200px) 690px, (min-width: 780px) calc(-5vw + 669px), (min-width: 580px) 510px, calc(100vw - 30px)">
      
    </figure>
  

  <h1>Build a Python MCP Client to Test Servers From Your Terminal</h1>
  <div class="mb-0">
    
      <span class="text-muted">by <a class="text-muted" href="#author">Leodanis Pozo Ramos</a>
    
    
      <span class="icon baseline ml-2"><svg role="img"><use href="/static/icons.0fae35ff985f.svg#@date"/></svg><span class="sr-only">Publication date</span></span> Nov 19, 2025
    
    
      <span class="icon baseline ml-2"><svg role="img"><use href="/static/icons.0fae35ff985f.svg#regular--book-open"/></svg><span class="sr-only">Reading time estimate</span></span> 31m
    
    
      <span class="icon baseline ml-2 mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--comments"/></svg></span><a class="text-muted" href="#reader-comments"><span class="disqus-comment-count" data-disqus-identifier="https://realpython.com/python-mcp-client/"></span></a>
    
    

    <span class="d-inline d-md-block"> 
      <span class="icon baseline ml-2 ml-md-0" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@category"/></svg></span>
      
        
<a href="/tutorials/intermediate/" class="badge badge-light text-muted" data-previewable>intermediate</a>

      
        
<a href="/tutorials/ai/" class="badge badge-light text-muted" data-previewable>ai</a>

      
        
<a href="/tutorials/projects/" class="badge badge-light text-muted" data-previewable>projects</a>

      
    </span>

    <div class="d-flex justify-content-between gap-2 my-3">
      


<completion-status
  class="btn-group mb-0"
  article-slug="python-mcp-client"
  
  
  
  data-api-article-bookmark-url="/api/v1/articles/python-mcp-client/bookmark/"
  data-api-article-completion-status-url="/api/v1/articles/python-mcp-client/completion_status/">
  <button
    data-target-complete-button
    title="Click to mark as completed"
    class="btn btn-secondary border-right"
    style="border-top-right-radius: 0; border-bottom-right-radius: 0;"
    disabled>Mark as Completed</button>
  <button
    data-target-bookmark-button
    title="Add bookmark"
    class="btn btn-secondary border-left"
    disabled><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#light--bookmark"/></svg></span></button>
</completion-status>

      
        









<share-button
  url="https://realpython.com/python-mcp-client/"
  url-title="Build a Python MCP Client to Test Servers From Your Terminal"
  twitter-text="Interesting #Python article by @realpython:"
  bluesky-text="Interesting #Python article by @realpython.com:"
  email-subject="Python article for you"
  email-body="Check out this Python article:%0A%0ABuild a Python MCP Client to Test Servers From Your Terminal"
>
  <button class="btn btn-secondary" type="button" title="Share" aria-label="Share"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@share-light"/></svg></span>Share</button>
</share-button>


      
    </div>
  </div>

  

  <div class="article-body">
    
      <div class="bg-light sidebar-module sidebar-module-inset" id="toc">
        <p class="h3 mb-2 text-muted">Table of Contents</p>
        <div class="toc">
<ul>
<li><a href="#prerequisites">Prerequisites</a></li>
<li><a href="#step-1-set-up-the-project-and-the-environment">Step 1: Set Up the Project and the Environment</a></li>
<li><a href="#step-2-write-a-minimal-mcp-client-with-the-python-sdk">Step 2: Write a Minimal MCP Client With the Python SDK</a></li>
<li><a href="#step-3-discover-server-tools-prompts-and-resources">Step 3: Discover Server Tools, Prompts, and Resources</a><ul>
<li><a href="#build-the-mcp-clients-cli-with-argparse">Build the MCP Client&rsquo;s CLI With argparse</a></li>
<li><a href="#add-the-members-command-line-option">Add the --members Command-Line Option</a></li>
<li><a href="#update-the-clients-entry-point-script">Update the Client&rsquo;s Entry-Point Script</a></li>
</ul>
</li>
<li><a href="#step-4-add-an-ai-powered-chat">Step 4: Add an AI-Powered Chat</a><ul>
<li><a href="#add-the-chat-command-line-option">Add the --chat Command-Line Option</a></li>
<li><a href="#create-a-chat-handler-with-openais-python-sdk">Create a Chat Handler With OpenAI&rsquo;s Python SDK</a></li>
<li><a href="#connect-the-chat-with-the-mcp-client">Connect the Chat With the MCP Client</a></li>
<li><a href="#update-the-clients-entry-point-script_1">Update the Client&rsquo;s Entry-Point Script</a></li>
<li><a href="#make-the-mcp-client-app-installable">Make the MCP Client App Installable</a></li>
</ul>
</li>
<li><a href="#conclusion">Conclusion</a></li>
<li><a href="#next-steps">Next Steps</a></li>
<li><a href="#frequently-asked-questions">Frequently Asked Questions</a></li>
</ul>
</div>

      </div>
    

    
    <div class="sidebar-module sidebar-module-inset p-0" style="overflow:hidden;">
      

<div style="display:block;position:relative;">
  <div style="display:block;width:100%;padding-top:12.5%;"></div>
  <div class="rpad rounded border" data-unit="8x1" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;"></div>
</div>
<a class="small text-muted" href="/account/join/" rel="nofollow"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--circle-info"/></svg></span>Remove ads</a>


    </div>
    

    

    <p>Building an MCP client in Python can be a good option when you&rsquo;re coding MCP servers and want a quick way to test them. In this step-by-step project, you&rsquo;ll build a minimal MCP client for the command line. It&rsquo;ll be able to connect to an MCP server through the standard input/output (stdio) transport, list the server&rsquo;s capabilities, and use the server&rsquo;s tools to feed an AI-powered chat.</p>
<p><strong>By the end of this tutorial, you&rsquo;ll understand that:</strong></p>
<ul>
<li>You can build an <strong>MCP client</strong> app for the command line using the <strong>MCP Python SDK</strong> and <code>argparse</code>.</li>
<li>You can list a <strong>server&rsquo;s capabilities</strong> by calling <code>.list_tools()</code>, <code>.list_prompts()</code>, and <code>.list_resources()</code> on a <code>ClientSession</code> instance.</li>
<li>You can use the <strong>OpenAI Python SDK</strong> to integrate MCP <strong>tool responses</strong> into an AI-powered chat session.</li>
</ul>
<p>Next, you&rsquo;ll move through setup, client implementation, capability discovery, chat handling, and packaging to test MCP servers from your terminal.</p>
<section class="section2"><h2 id="prerequisites">Prerequisites<a class="headerlink" href="#prerequisites" title="Permanent link"></a></h2>
<p>To get the most out of this coding project, you should have some previous knowledge of how to manage a Python project with <code>uv</code>. You should also know the basics of working with the <a href="/ref/stdlib/asyncio/" class="ref-link"><code>asyncio</code></a> and <a href="/ref/stdlib/argparse/" class="ref-link"><code>argparse</code></a> libraries from the <a href="/ref/glossary/standard-library/" class="ref-link">standard library</a>.</p>
<p>To satisfy these knowledge requirements, you can take a look at the following resources:</p>
<ul>
<li><a href="https://realpython.com/python-uv/">Managing Python Projects With <code>uv</code>: An All-in-One Solution</a></li>
<li><a href="https://realpython.com/async-io-python/">Python&rsquo;s <code>asyncio</code>: A Hands-On Walkthrough</a></li>
<li><a href="https://realpython.com/command-line-interfaces-python-argparse/">Build Command-Line Interfaces With Python&rsquo;s <code>argparse</code></a></li>
</ul>
<p>Familiarity with OpenAI&rsquo;s Python API, <a href="https://github.com/openai/openai-python"><code>openai</code></a>, will also be helpful because you&rsquo;ll use this library to power the chat functionality of your MCP client. You&rsquo;ll also use the <a href="/ref/ai-coding-glossary/mcp/" class="ref-link">Model Context Protocol (MCP)</a> Python <a href="https://github.com/modelcontextprotocol/python-sdk">SDK</a>.</p>
<p>Don&rsquo;t worry if you don&rsquo;t have all of the prerequisite knowledge before starting this tutorial&mdash;that&rsquo;s completely okay! You&rsquo;ll learn through the process of getting your hands dirty as you build the project. If you get stuck, then take some time to review the resources linked above. Then, get back to the code.</p>
<p>You&rsquo;ll also need an MCP server to try your client as you build it. Don&rsquo;t worry if you don&rsquo;t have one available&mdash;you can use the server provided in <a href="#step-2-write-a-minimal-mcp-client-with-the-python-sdk">step 2</a>.</p>
<p>In this tutorial, you won&rsquo;t get into the details of creating MCP servers. To learn more about this topic, check out the <a href="https://realpython.com/python-mcp/">Python MCP Server: Connect LLMs to Your Data</a> tutorial. Finally, you can download the project&rsquo;s source code and related files by clicking the link below.</p>
<div class="alert alert-warning" role="alert">
<p><strong markdown>Get Your Code:</strong> <a href="https://realpython.com/bonus/python-mcp-client-code/" class="alert-link" data-toggle="modal" data-target="#modal-python-mcp-client-code" markdown>Click here to download the free sample code</a> you&rsquo;ll use to build a Python MCP client to test servers from your terminal.</p>
</div>
<div class="container border rounded text-wrap-pretty my-3">

  <p class="my-3"><mark class="marker-highlight"><strong><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@quiz"/></svg></span> Take the Quiz:</strong></mark> Test your knowledge with our interactive “Build a Python MCP Client to Test Servers From Your Terminal” quiz. You’ll receive a score upon completion to help you track your learning progress:</p>

  <hr>

  <div class="row my-3">
    <div class="col-xs-12 col-sm-4 col-md-3 align-self-center">

      <a href="/quizzes/python-mcp-client/" tabindex="-1">
        <div class="embed-responsive embed-responsive-16by9">

            <img class="card-img-top m-0 p-0 embed-responsive-item rounded" style="object-fit: contain; background: #ffc973;" alt="Build a Python MCP Client to Test Servers From Your Terminal" src="https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg" width="1920" height="1080" srcset="/cdn-cgi/image/width=480,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 480w, /cdn-cgi/image/width=640,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 640w, /cdn-cgi/image/width=960,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 960w, /cdn-cgi/image/width=1920,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 1920w" sizes="(min-width: 1200px) 142px, (min-width: 1000px) 122px, (min-width: 780px) 112px, (min-width: 580px) 139px, calc(100vw - 62px)">


          <div class="card-img-overlay d-flex align-items-center">
            <div class="mx-auto">
              <span class="text-light" style="opacity: 0.90;"><span class="icon baseline scale2x" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@quiz"/></svg></span></span>
            </div>
          </div>
        </div>
      </a>

    </div>
    <div class="col">
      <div class="mt-3 d-md-none"></div> 
      <p class="small text-muted mb-0"><strong>Interactive Quiz</strong></p>
      <a href="/quizzes/python-mcp-client/" class="stretched-link"><span class="my-0 h4">Build a Python MCP Client to Test Servers From Your Terminal</span></a> 
      <p class="text-muted mb-0 small">Learn how to create a Python MCP client, start an AI-powered chat session, and run it from the command line. Check your understanding.</p>
    </div>
  </div>

</div>

<div><div class="rounded border border-light" style="display:block;position:relative;"> <div style="display:block;width:100%;padding-top:12.5%;"></div> <div class="rpad rounded border" data-unit="8x1" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;"> </div></div><a class="small text-muted" href="/account/join/" rel="nofollow"> <span aria-hidden="true" class="icon baseline mr-1"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--circle-info"></use></svg></span>Remove ads</a></div></section><section class="section2"><h2 id="step-1-set-up-the-project-and-the-environment">Step 1: Set Up the Project and the Environment<a class="headerlink" href="#step-1-set-up-the-project-and-the-environment" title="Permanent link"></a></h2>
<p>To manage your MCP client project, you&rsquo;ll use <code>uv</code>, a command-line tool for Python project management. If you don&rsquo;t have this tool on your current system, then it&rsquo;s worth checking out the <a href="https://realpython.com/python-uv/">Managing Python Projects With <code>uv</code>: An All-in-One Solution</a> tutorial.</p>
<div class="alert alert-primary" role="alert">
<p><strong>Note:</strong> If you prefer not to use <code>uv</code>, then you can use a combination of alternative tools such as <a href="https://realpython.com/intro-to-pyenv/"><code>pyenv</code></a>, <a href="/ref/stdlib/venv/" class="ref-link"><code>venv</code></a>, <a href="https://realpython.com/what-is-pip/"><code>pip</code></a>, or <a href="https://realpython.com/dependency-management-python-poetry/"><code>poetry</code></a>.</p>
</div>
<p>Once you have <code>uv</code> or another tool set up, go ahead and open a terminal window. Then, move to a directory where you typically store your projects. From there, run the following commands to scaffold and initialize a new <code>mcp-client/</code> project:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="console" data-is-repl="true">
  <div class="codeblock__header codeblock--yellow">
    <span class="mr-2 noselect" aria-label="Language">Shell</span>
    
    <div class="noselect">
      
        <span class="codeblock__output-toggle" title="Toggle prompts and output" role="button"><span class="icon baseline js-codeblock-output-on codeblock__header--icon-lower" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#regular--rectangle-terminal"/></svg></span></span>
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="gp">$ </span>uv<span class="w"> </span>init<span class="w"> </span>mcp-client
<span class="gp">$ </span><span class="nb">cd</span><span class="w"> </span>mcp-client/
<span class="gp">$ </span>uv<span class="w"> </span>add<span class="w"> </span>mcp<span class="w"> </span>openai
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>The first command creates a new Python project in an <code>mcp-client/</code> directory. The resulting directory will have the following structure:</p>
<div class="highlight"><pre><span></span><code>mcp-client/
├── .git/
├── .gitignore
├── .python-version
├── README.md
├── main.py
└── pyproject.toml
</code></pre></div>
<p>First, you have the <code>.git/</code> directory and the <code>.gitignore</code> file, which will help you version-control the project.</p>
<p>The <code>.python-version</code> file contains the default Python version for the current project. This file tells <code>uv</code> which Python version to use when creating a dedicated <a href="https://realpython.com/python-virtual-environments-a-primer/">virtual environment</a> for the project. This file will contain the version number of the Python interpreter you&rsquo;re currently using.</p>
<p>Next, you have an empty <a href="https://realpython.com/readme-python-project/"><code>README.md</code></a> file that you can use to provide basic documentation for your project. The <code>main.py</code> file provides a Python script that you can optionally use as the project&rsquo;s entry point. You won&rsquo;t use this file in this tutorial, so feel free to remove it.</p>
<p>Finally, you have the <a href="https://realpython.com/python-pyproject-toml/"><code>pyproject.toml</code></a> file, which you&rsquo;ll use to prepare your project for building and distribution.</p>
<p>The <code>uv add</code> command allows you to install external dependencies. In this project, you&rsquo;ll need <code>mcp</code> and <code>openai</code>, which are available in the <a href="/ref/glossary/pypi/" class="ref-link">Python Package Index (PyPI)</a>. When you run this command, <code>uv</code> automatically creates a virtual environment for the project.</p>
<div class="alert alert-primary" role="alert">
<p><strong>Note:</strong> By the end of this tutorial, your project&rsquo;s folder will have the following structure:</p>
<div class="highlight"><pre><span></span><code>mcp-client/
├── .git/
├── .venv/
├── mcp_client/
│   ├── __init__.py
│   ├── __main__.py
│   ├── chat.py
│   ├── cli.py
│   ├── handlers.py
│   └── mcp_client.py
├── mcp_server/
│   └── mcp_server.py
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── uv.lock
</code></pre></div>
<p>You can create the missing subfolders and files now if you like, or you can defer this task until you need them. For now, note the subtle difference between the project root folder, <code>mcp-client/</code>, and the client package folder, <code>mcp_client/</code>.</p>
</div>
<p>Now, you can launch your favorite <a href="https://realpython.com/python-ides-code-editors-guide/">code editor or IDE</a> in the project&rsquo;s root directory, and you&rsquo;re ready to start coding!</p>
</section><section class="section2"><h2 id="step-2-write-a-minimal-mcp-client-with-the-python-sdk">Step 2: Write a Minimal MCP Client With the Python SDK<a class="headerlink" href="#step-2-write-a-minimal-mcp-client-with-the-python-sdk" title="Permanent link"></a></h2>
<p>Before you start coding your MCP client, you need to set up the test server. If you don&rsquo;t have one, then go ahead and create an <code>mcp_server/</code> subdirectory in the project&rsquo;s root folder. Then, create the <code>mcp_server.py</code> script with the code below:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_server/mcp_server.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">mcp.server.fastmcp</span><span class="w"> </span><span class="kn">import</span> <span class="n">FastMCP</span>

<span class="n">mcp</span> <span class="o">=</span> <span class="n">FastMCP</span><span class="p">(</span><span class="s2">&quot;mcp_server&quot;</span><span class="p">)</span>

<span class="nd">@mcp</span><span class="o">.</span><span class="n">tool</span><span class="p">()</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">echo</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;Echo back the message.&quot;&quot;&quot;</span>
    <span class="k">return</span> <span class="n">message</span>

<span class="nd">@mcp</span><span class="o">.</span><span class="n">prompt</span><span class="p">()</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">greeting_prompt</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;A simple greeting prompt.&quot;&quot;&quot;</span>
    <span class="k">return</span> <span class="sa">f</span><span class="s2">&quot;Greet </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> kindly.&quot;</span>

<span class="nd">@mcp</span><span class="o">.</span><span class="n">resource</span><span class="p">(</span><span class="s2">&quot;file://./greeting.txt&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">greeting_file</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;The greeting text file.&quot;&quot;&quot;</span>
    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&quot;greeting.txt&quot;</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">file</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">file</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&quot;__main__&quot;</span><span class="p">:</span>
    <span class="n">mcp</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">transport</span><span class="o">=</span><span class="s2">&quot;stdio&quot;</span><span class="p">)</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>Now you&rsquo;re all set up to create a minimal MCP client that can connect to this server and others. Go ahead and create an <code>mcp_client/</code> subdirectory in the project&rsquo;s root. This directory will be a Python package, so you need to create the corresponding <a href="https://realpython.com/python-init-py/"><code>__init__.py</code></a> file. You also need to create the <code>mcp_client.py</code> file with this initial content:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/mcp_client.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">sys</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">contextlib</span><span class="w"> </span><span class="kn">import</span> <span class="n">AsyncExitStack</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Awaitable</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">ClassVar</span><span class="p">,</span> <span class="n">Self</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">mcp</span><span class="w"> </span><span class="kn">import</span> <span class="n">ClientSession</span><span class="p">,</span> <span class="n">StdioServerParameters</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">mcp.client.stdio</span><span class="w"> </span><span class="kn">import</span> <span class="n">stdio_client</span>

<span class="k">class</span><span class="w"> </span><span class="nc">MCPClient</span><span class="p">:</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;MCP client to interact with MCP server.</span>

<span class="sd">    Usage:</span>
<span class="sd">        async with MCPClient(server_path) as client:</span>
<span class="sd">            # Call client methods here...</span>
<span class="sd">    &quot;&quot;&quot;</span>

    <span class="n">client_session</span><span class="p">:</span> <span class="n">ClassVar</span><span class="p">[</span><span class="n">ClientSession</span><span class="p">]</span>

    <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">server_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">server_path</span> <span class="o">=</span> <span class="n">server_path</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">exit_stack</span> <span class="o">=</span> <span class="n">AsyncExitStack</span><span class="p">()</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>In this code, you start by <a href="https://realpython.com/python-import/">importing</a> the required <a href="/ref/glossary/module/" class="ref-link">modules</a> and <a href="/ref/glossary/object/" class="ref-link">objects</a>. Next, you define the <code>MCPClient</code> <a href="https://realpython.com/python-classes/">class</a> to manage communication with the target MCP server. You&rsquo;ll handle this communication through <code>.client_session</code>, which is a <a href="https://realpython.com/python-classes/#class-attributes">class attribute</a> that holds an instance of <code>mcp.ClientSession</code>.</p>
<div class="alert alert-primary" role="alert">
<p><strong>Note:</strong> MCP supports two <a href="https://modelcontextprotocol.io/docs/learn/architecture#transport-layer">transport mechanisms</a>:</p>
<ol>
<li><a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#stdio">Stdio transport</a>: Uses standard input/output streams for direct communication between local processes on the same machine.</li>
<li><a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http">Streamable HTTP transport</a>: Uses HTTP POST requests for client-to-server messages, enabling communication with remote servers.</li>
</ol>
<p>In this tutorial, you&rsquo;ll use the stdio transport mechanism to connect the MCP client to a test server. As a further exercise, you can explore the HTTP transport for remote communication.</p>
</div>
<p>The <a href="https://realpython.com/python-class-constructor/#object-initialization-with-__init__"><code>.__init__()</code> method</a> initializes the server&rsquo;s executable path and creates an <code>AsyncExitStack</code> instance. <code>AsyncExitStack</code> is a context manager from Python&rsquo;s <a href="/ref/stdlib/contextlib/" class="ref-link"><code>contextlib</code></a> module that allows you to programmatically manage multiple <a href="/ref/glossary/asynchronous-context-manager/" class="ref-link">asynchronous context managers</a>. You need this class to manage the stdio client and its session.</p>
<p>You&rsquo;ll use the <code>MCPClient</code> class as an asynchronous context manager, so you need to implement the <code>.__aenter__()</code> and <code>.__aexit__()</code> special methods:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/mcp_client.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">MCPClient</span><span class="p">:</span>
    <span class="c1"># ...</span>

    <span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="fm">__aenter__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Self</span><span class="p">:</span>
        <span class="bp">cls</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
        <span class="bp">cls</span><span class="o">.</span><span class="n">client_session</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_connect_to_server</span><span class="p">()</span>
        <span class="k">return</span> <span class="bp">self</span>

    <span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="fm">__aexit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">_</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
        <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">exit_stack</span><span class="o">.</span><span class="n">aclose</span><span class="p">()</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>The <code>.__aenter__()</code> method runs when the <a href="https://realpython.com/python-with-statement/#using-the-async-with-statement"><code>async with</code> statement</a> enters the target context. This is a good time to create the client session, which you do by calling the <code>._connect_to_server()</code> helper method. You&rsquo;ll implement this method in a moment.</p>
<p>Because <code>.client_session</code> is a class attribute, you use the class object (<code>cls</code>) to assign it a value. Note that using <a href="/ref/glossary/self/" class="ref-link"><code>self</code> (argument)</a> instead of <a href="/ref/glossary/cls/" class="ref-link"><code>cls</code> (argument)</a> will create a new instance attribute, rather than referring to the class attribute.</p>
<p>The <code>.client_session</code> attribute allows you to create and manage a client session over standard <a href="/ref/glossary/input-output/" class="ref-link">input/output (I/O)</a> using the <code>stdio_client()</code> function.</p>
<p>The <code>.__aexit__()</code> method is called automatically when the <code>async with</code> statement exits. This is the right moment to close the client session: call <code>.aclose()</code> on the <code>AsyncExitStack</code> and await it asynchronously to release the resources cleanly.</p>
<p>Now it&rsquo;s time to implement <code>._connect_to_server()</code> to perform the actual job. The method will be asynchronous and return a <code>ClientSession</code> instance:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/mcp_client.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">MCPClient</span><span class="p">:</span>
    <span class="c1"># ...</span>

    <span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_connect_to_server</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ClientSession</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">read</span><span class="p">,</span> <span class="n">write</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">exit_stack</span><span class="o">.</span><span class="n">enter_async_context</span><span class="p">(</span>
                <span class="n">stdio_client</span><span class="p">(</span>
                    <span class="n">server</span><span class="o">=</span><span class="n">StdioServerParameters</span><span class="p">(</span>
                        <span class="n">command</span><span class="o">=</span><span class="s2">&quot;sh&quot;</span><span class="p">,</span>
                        <span class="n">args</span><span class="o">=</span><span class="p">[</span>
                            <span class="s2">&quot;-c&quot;</span><span class="p">,</span>
                            <span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">executable</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">server_path</span><span class="si">}</span><span class="s2"> 2&gt;/dev/null&quot;</span><span class="p">,</span>
                        <span class="p">],</span>
                        <span class="n">env</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
                    <span class="p">)</span>
                <span class="p">)</span>
            <span class="p">)</span>
            <span class="n">client_session</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">exit_stack</span><span class="o">.</span><span class="n">enter_async_context</span><span class="p">(</span>
                <span class="n">ClientSession</span><span class="p">(</span><span class="n">read</span><span class="p">,</span> <span class="n">write</span><span class="p">)</span>
            <span class="p">)</span>
            <span class="k">await</span> <span class="n">client_session</span><span class="o">.</span><span class="n">initialize</span><span class="p">()</span>
            <span class="k">return</span> <span class="n">client_session</span>
        <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
            <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s2">&quot;Error: Failed to connect to server&quot;</span><span class="p">)</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>In the <code>._connect_to_server()</code> method, you establish a connection between <code>MCPClient</code> and an MCP server process.</p>
<p>Inside the <code>try</code> block, you use the <code>.exit_stack</code> object to enter an asynchronous context and access the read and write streams of your client&rsquo;s standard I/O.</p>
<p>The client takes a server whose parameters are the shell (<code>sh</code>) command with the <code>-c</code> option to execute a string as a command. You build the target string using the Python executable, <a href="https://docs.python.org/3/library/sys.html#sys.executable"><code>sys.executable</code></a>, and the <code>self.server_path</code> attribute. The <code>2&gt;/dev/null</code> bit redirects any error output so the terminal window doesn&rsquo;t get cluttered.</p>
<p>Then, you pass the read and write communication channels to the <code>ClientSession</code> class, which manages the communication session. Finally, you initialize the session and return it to the caller.</p>
<p>The <code>AsyncExitStack</code> instance enables you to maintain the stdio client and its session in context throughout the app&rsquo;s execution. You only exit both contexts when you call <code>.exit_stack.aclose()</code> in the <code>.__aexit__()</code> method.</p>
<p>If any step fails, you raise a <a href="/ref/builtin-exceptions/runtimeerror/" class="ref-link"><code>RuntimeError</code></a> to indicate that the connection attempt was unsuccessful. Note that catching the broad <a href="/ref/builtin-exceptions/exception/" class="ref-link"><code>Exception</code></a> class isn&rsquo;t a best practice in Python. However, in this example, you use this exception for the sake of simplicity and convenience.</p>
<p>Now it&rsquo;s time to give this code a first try. Return to your code editor and create the following <code>__main__.py</code> file inside the <code>mcp_client</code> package:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/__main__.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">asyncio</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">mcp_client.mcp_client</span><span class="w"> </span><span class="kn">import</span> <span class="n">MCPClient</span>

<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">MCPClient</span><span class="p">(</span><span class="s2">&quot;./mcp_server/mcp_server.py&quot;</span><span class="p">):</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;Connected to the MCP server!&quot;</span><span class="p">)</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&quot;__main__&quot;</span><span class="p">:</span>
    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>This file will eventually become the client&rsquo;s entry-point script. For now, it only provides some boilerplate code that allows you to test the client&rsquo;s current functionality. Note that you&rsquo;ve also hard-coded the server path to simplify the code. You&rsquo;ll fix this in the next section.</p>
<div class="alert alert-primary" role="alert">
<p><strong>Note:</strong> At this point in the development process, your project&rsquo;s directory structure should look something like this:</p>
<div class="highlight"><pre><span></span><code>mcp-client/
├── .git/
├── .venv/
<span class="hll">├── mcp_client/
</span><span class="hll">│   ├── __init__.py
</span><span class="hll">│   ├── __main__.py
</span><span class="hll">│   └── mcp_client.py
</span><span class="hll">├── mcp_server/
</span><span class="hll">│   └── mcp_server.py
</span>├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── uv.lock
</code></pre></div>
</div>
<p>Go ahead and run the following command in your terminal:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="console" data-is-repl="true">
  <div class="codeblock__header codeblock--yellow">
    <span class="mr-2 noselect" aria-label="Language">Shell</span>
    
    <div class="noselect">
      
        <span class="codeblock__output-toggle" title="Toggle prompts and output" role="button"><span class="icon baseline js-codeblock-output-on codeblock__header--icon-lower" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#regular--rectangle-terminal"/></svg></span></span>
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="gp">$ </span>uv<span class="w"> </span>run<span class="w"> </span>python<span class="w"> </span>-m<span class="w"> </span>mcp_client
<span class="go">Connected to the MCP server!</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>Your MCP client successfully connects to the target server. That&rsquo;s a great accomplishment! In the next section, you&rsquo;ll add a command-line interface (CLI) to your client and some more useful functionality.</p>
<div><div class="rounded border border-light" style="display:block;position:relative;"> <div style="display:block;width:100%;padding-top:12.5%;"></div> <div class="rpad rounded border" data-unit="8x1" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;"> </div></div><a class="small text-muted" href="/account/join/" rel="nofollow"> <span aria-hidden="true" class="icon baseline mr-1"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--circle-info"></use></svg></span>Remove ads</a></div></section><section class="section2"><h2 id="step-3-discover-server-tools-prompts-and-resources">Step 3: Discover Server Tools, Prompts, and Resources<a class="headerlink" href="#step-3-discover-server-tools-prompts-and-resources" title="Permanent link"></a></h2>
<p>To add a CLI to your MCP client, you&rsquo;ll use the <a href="/ref/stdlib/argparse/" class="ref-link"><code>argparse</code></a> module. This module is part of the Python standard library and provides a quick way to create user-friendly CLIs with arguments, options, subcommands, and more.</p>
<p>In this initial stage, the CLI will take the path to the MCP server script as an argument. It&rsquo;ll also have a <code>--members</code> option to list the server&rsquo;s tools, prompts, and resources. Each of these displays a short description if available.</p>
<section class="section3"><h3 id="build-the-mcp-clients-cli-with-argparse">Build the MCP Client&rsquo;s CLI With <code>argparse</code><a class="headerlink" href="#build-the-mcp-clients-cli-with-argparse" title="Permanent link"></a></h3>
<p>Get back to your code editor and create a new file called <code>cli.py</code> inside the <code>mcp_client</code> package. Then, add the following content to the file:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/cli.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">argparse</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">pathlib</span>

<span class="k">def</span><span class="w"> </span><span class="nf">parse_args</span><span class="p">():</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;Parse command line arguments and return parsed args.&quot;&quot;&quot;</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&quot;A minimal MCP client&quot;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
        <span class="s2">&quot;server_path&quot;</span><span class="p">,</span>
        <span class="nb">type</span><span class="o">=</span><span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">,</span>
        <span class="n">help</span><span class="o">=</span><span class="s2">&quot;path to the MCP server script&quot;</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="n">group</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_mutually_exclusive_group</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">group</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
        <span class="s2">&quot;--members&quot;</span><span class="p">,</span>
        <span class="n">action</span><span class="o">=</span><span class="s2">&quot;store_true&quot;</span><span class="p">,</span>
        <span class="n">help</span><span class="o">=</span><span class="s2">&quot;list the MCP server&#39;s tools, prompts, and resources&quot;</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>In this file, you create a command-line argument parser using the <code>ArgumentParser</code> class. Then, you add an argument to specify the path to the MCP server&rsquo;s script. You set the argument&rsquo;s type to <a href="https://realpython.com/python-pathlib/"><code>pathlib.Path</code></a> so that the CLI converts the user-provided path string into a <code>Path</code> object, enhancing the app&rsquo;s path management. The <code>help</code> argument allows you to provide descriptive help messages for the client users.</p>
<p>You also create a group of <a href="https://realpython.com/command-line-interfaces-python-argparse/#defining-mutually-exclusive-argument-and-option-groups">mutually exclusive command-line options</a>. You need this because you don&rsquo;t want the <code>--members</code> option to run with the <code>--chat</code> option that you&rsquo;ll implement later. Next, you add the <code>--members</code> option as a stored <a href="/ref/glossary/boolean/" class="ref-link">Boolean</a> value with an appropriate help message. That&rsquo;s it! The CLI is ready for now.</p>
<div class="alert alert-primary" role="alert">
<p><strong>Note:</strong> At this point, your project&rsquo;s root directory should look something like this:</p>
<div class="highlight"><pre><span></span><code>mcp-client/
├── .git/
├── .venv/
├── mcp_client/
│   ├── __init__.py
│   ├── __main__.py
<span class="hll">│   ├── cli.py
</span>│   └── mcp_client.py
├── mcp_server/
│   └── mcp_server.py
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── uv.lock
</code></pre></div>
</div>
<p>In the next section, you&rsquo;ll write the code that&rsquo;ll make the <code>--members</code> option list the MCP server&rsquo;s tools, prompts, and resources.</p>
</section><section class="section3"><h3 id="add-the-members-command-line-option">Add the <code>--members</code> Command-Line Option<a class="headerlink" href="#add-the-members-command-line-option" title="Permanent link"></a></h3>
<p>The <code>--members</code> command-line option will provide a complete list of the target MCP server&rsquo;s members or primitives. You&rsquo;ll start with the <code>list_all_members()</code> function, which will be part of the public interface of <code>MCPClient</code>:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/mcp_client.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">MCPClient</span><span class="p">:</span>
    <span class="c1"># ...</span>

    <span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">list_all_members</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="w">        </span><span class="sd">&quot;&quot;&quot;List all available tools, prompts, and resources.&quot;&quot;&quot;</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;MCP Server Members&quot;</span><span class="p">)</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;=&quot;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>

        <span class="n">sections</span> <span class="o">=</span> <span class="p">{</span>
            <span class="s2">&quot;tools&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">client_session</span><span class="o">.</span><span class="n">list_tools</span><span class="p">,</span>
            <span class="s2">&quot;prompts&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">client_session</span><span class="o">.</span><span class="n">list_prompts</span><span class="p">,</span>
            <span class="s2">&quot;resources&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">client_session</span><span class="o">.</span><span class="n">list_resources</span><span class="p">,</span>
        <span class="p">}</span>
        <span class="k">for</span> <span class="n">section</span><span class="p">,</span> <span class="n">listing_method</span> <span class="ow">in</span> <span class="n">sections</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
            <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_list_section</span><span class="p">(</span><span class="n">section</span><span class="p">,</span> <span class="n">listing_method</span><span class="p">)</span>

        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span> <span class="o">+</span> <span class="s2">&quot;=&quot;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>In this function, you print a heading for the list of members. Then, you create a dictionary that maps the sections to the corresponding listing method in the client session. Next, you <a href="/ref/glossary/loop/" class="ref-link">loop</a> over the dictionary items to call <code>._list_section()</code> with the corresponding section name and method. This retrieves the data from the MCP server.</p>
<p>Here&rsquo;s the implementation of your <code>._list_section()</code> helper method. Because it&rsquo;s a <a href="https://realpython.com/python-double-underscore/#non-public-methods">non-public method</a>, it isn&rsquo;t part of the class&rsquo;s public interface:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/mcp_client.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">MCPClient</span><span class="p">:</span>
    <span class="c1"># ...</span>

    <span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_list_section</span><span class="p">(</span>
        <span class="bp">self</span><span class="p">,</span>
        <span class="n">section</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
        <span class="n">list_method</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="n">Awaitable</span><span class="p">[</span><span class="n">Any</span><span class="p">]],</span>
    <span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">items</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="k">await</span> <span class="n">list_method</span><span class="p">(),</span> <span class="n">section</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">items</span><span class="p">:</span>
                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="se">\n</span><span class="si">{</span><span class="n">section</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">items</span><span class="p">)</span><span class="si">}</span><span class="s2">):&quot;</span><span class="p">)</span>
                <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;-&quot;</span> <span class="o">*</span> <span class="mi">30</span><span class="p">)</span>
                <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">items</span><span class="p">:</span>
                    <span class="n">description</span> <span class="o">=</span> <span class="n">item</span><span class="o">.</span><span class="n">description</span> <span class="ow">or</span> <span class="s2">&quot;No description&quot;</span>
                    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot; &gt; </span><span class="si">{</span><span class="n">item</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> - </span><span class="si">{</span><span class="n">description</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="se">\n</span><span class="si">{</span><span class="n">section</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span><span class="si">}</span><span class="s2">: None available&quot;</span><span class="p">)</span>
        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="se">\n</span><span class="si">{</span><span class="n">section</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span><span class="si">}</span><span class="s2">: Error - </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>In the <code>._list_section()</code> method, you take a section name, such as <code>"tools"</code>, <code>"prompts"</code>, or <code>"resources"</code>, and a <code>ClientSession.list_*</code> method object as arguments. Then, you dynamically call the method using <a href="/ref/builtin-functions/getattr/" class="ref-link"><code>getattr()</code></a> and retrieve the members or items in the section. Finally, you print the section title, the number of items, and the details for each one, including its name and description.</p>
<p>If no items are found, you print a message indicating that the section is empty. The method also includes error handling to catch and display any exceptions that occur during the process, ensuring that failures don&rsquo;t crash the program but instead produce a clear error message for the user.</p>
</section><section class="section3"><h3 id="update-the-clients-entry-point-script">Update the Client&rsquo;s Entry-Point Script<a class="headerlink" href="#update-the-clients-entry-point-script" title="Permanent link"></a></h3>
<p>To make everything work, you need to fully update your <code>__main__.py</code> file with the following changes:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/__main__.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">asyncio</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">mcp_client.cli</span><span class="w"> </span><span class="kn">import</span> <span class="n">parse_args</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">mcp_client.mcp_client</span><span class="w"> </span><span class="kn">import</span> <span class="n">MCPClient</span>

<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;Run the MCP client with the specified options.&quot;&quot;&quot;</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">parse_args</span><span class="p">()</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">args</span><span class="o">.</span><span class="n">server_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Error: Server script &#39;</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">server_path</span><span class="si">}</span><span class="s2">&#39; not found&quot;</span><span class="p">)</span>
        <span class="k">return</span>

    <span class="k">try</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">MCPClient</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">server_path</span><span class="p">))</span> <span class="k">as</span> <span class="n">client</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">members</span><span class="p">:</span>
                <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">list_all_members</span><span class="p">()</span>
    <span class="k">except</span> <span class="ne">RuntimeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&quot;__main__&quot;</span><span class="p">:</span>
    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>In this update to the <code>main()</code> function, you grab the command-line arguments by calling <code>parse_args()</code> from the <code>cli.py</code> module. The <a href="https://realpython.com/python-conditional-statements/">conditional</a> checks whether the path to the MCP server script exists. If not, then you print an error message and return, which eventually causes the application to terminate.</p>
<p>Next, you have a <code>try</code> block where you create an <code>MCPClient</code> instance in an <code>async with</code> statement. The conditional inside the <code>with</code> code block checks whether the user provided the <code>--members</code> option at the command line. If that&rsquo;s the case, then you await the call to <code>.list_all_members()</code>. If an error occurs, you print the error message to the screen.</p>
<p>To try out your MCP client, make sure you&rsquo;re in the project&rsquo;s root folder and then run the following command:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="console" data-is-repl="true">
  <div class="codeblock__header codeblock--yellow">
    <span class="mr-2 noselect" aria-label="Language">Shell</span>
    
    <div class="noselect">
      
        <span class="codeblock__output-toggle" title="Toggle prompts and output" role="button"><span class="icon baseline js-codeblock-output-on codeblock__header--icon-lower" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#regular--rectangle-terminal"/></svg></span></span>
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="gp">$ </span>uv<span class="w"> </span>run<span class="w"> </span>python<span class="w"> </span>-m<span class="w"> </span>mcp_client<span class="w"> </span>mcp_server/mcp_server.py<span class="w"> </span>--members
<span class="go">MCP Server Members</span>
<span class="go">==================================================</span>

<span class="go">TOOLS (1):</span>
<span class="go">------------------------------</span>
<span class="go"> &gt; echo - Echo back the message.</span>

<span class="go">PROMPTS (1):</span>
<span class="go">------------------------------</span>
<span class="go"> &gt; greeting_prompt - A simple greeting prompt.</span>

<span class="go">RESOURCES (1):</span>
<span class="go">------------------------------</span>
<span class="go"> &gt; greeting_file - The greeting text file.</span>

<span class="go">==================================================</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>The output looks great! It lists the three sections along with their respective items. This allows you to assess the target server&rsquo;s capabilities and gain a better understanding of how to use it effectively.</p>
<div><div class="rounded border border-light" style="display:block;position:relative;"> <div style="display:block;width:100%;padding-top:12.5%;"></div> <div class="rpad rounded border" data-unit="8x1" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;"> </div></div><a class="small text-muted" href="/account/join/" rel="nofollow"> <span aria-hidden="true" class="icon baseline mr-1"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--circle-info"></use></svg></span>Remove ads</a></div></section></section><section class="section2"><h2 id="step-4-add-an-ai-powered-chat">Step 4: Add an AI-Powered Chat<a class="headerlink" href="#step-4-add-an-ai-powered-chat" title="Permanent link"></a></h2>
<p>In this section, you&rsquo;ll add chat capabilities to your MCP client. To do this, you&rsquo;ll start by adding a <code>--chat</code> option to the app&rsquo;s CLI. Then, you&rsquo;ll write an AI-powered chat handler using OpenAI&rsquo;s Python SDK. Finally, you&rsquo;ll set up the client for installation in your current Python environment.</p>
<p>To kick things off, you&rsquo;ll add the <code>--chat</code> command-line option to the app&rsquo;s CLI.</p>
<section class="section3"><h3 id="add-the-chat-command-line-option">Add the <code>--chat</code> Command-Line Option<a class="headerlink" href="#add-the-chat-command-line-option" title="Permanent link"></a></h3>
<p>Go back to the <code>cli.py</code> file and add the following code to it:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/cli.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">argparse</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">pathlib</span>

<span class="k">def</span><span class="w"> </span><span class="nf">parse_args</span><span class="p">():</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;Parse command line arguments and return parsed args.&quot;&quot;&quot;</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&quot;A minimal MCP client&quot;</span><span class="p">)</span>
    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
        <span class="s2">&quot;server_path&quot;</span><span class="p">,</span>
        <span class="nb">type</span><span class="o">=</span><span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">,</span>
        <span class="n">help</span><span class="o">=</span><span class="s2">&quot;path to the MCP server script&quot;</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="n">group</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">add_mutually_exclusive_group</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="n">group</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
        <span class="s2">&quot;--members&quot;</span><span class="p">,</span>
        <span class="n">action</span><span class="o">=</span><span class="s2">&quot;store_true&quot;</span><span class="p">,</span>
        <span class="n">help</span><span class="o">=</span><span class="s2">&quot;list the MCP server&#39;s tools, prompts, and resources&quot;</span><span class="p">,</span>
    <span class="p">)</span>
<span class="hll">    <span class="n">group</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
</span><span class="hll">        <span class="s2">&quot;--chat&quot;</span><span class="p">,</span>
</span><span class="hll">        <span class="n">action</span><span class="o">=</span><span class="s2">&quot;store_true&quot;</span><span class="p">,</span>
</span><span class="hll">        <span class="n">help</span><span class="o">=</span><span class="s2">&quot;start an AI-powered chat with MCP server integration&quot;</span><span class="p">,</span>
</span><span class="hll">    <span class="p">)</span>
</span>    <span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>The <code>--chat</code> option will trigger a minimal chat interface on the command line. Next, you&rsquo;ll set up the chat interface where you&rsquo;ll be able to enter your queries and receive the AI-powered backend&rsquo;s response.</p>
<p>To write the chat interface, go ahead and create a new file called <code>chat.py</code> under the <code>mcp_client</code> package. Then, add the following code to it:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/chat.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">run_chat</span><span class="p">(</span><span class="n">handler</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;Run an AI-handled chat session.&quot;&quot;&quot;</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">MCP Client&#39;s Chat Started!&quot;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;Type your queries or &#39;quit&#39; to exit.&quot;</span><span class="p">)</span>

    <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="n">query</span> <span class="o">:=</span> <span class="nb">input</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">You: &quot;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()):</span>
                <span class="k">continue</span>
            <span class="k">if</span> <span class="n">query</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&quot;quit&quot;</span><span class="p">:</span>
                <span class="k">break</span>

            <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span> <span class="o">+</span> <span class="k">await</span> <span class="n">handler</span><span class="o">.</span><span class="n">process_query</span><span class="p">(</span><span class="n">query</span><span class="p">))</span>
        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">Error: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>

    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">Goodbye!&quot;</span><span class="p">)</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>This is quite a short function. It takes an AI-powered handler as an argument, prints an informative message, and starts the chat loop.</p>
<p>The <a href="https://realpython.com/python-while-loop/"><code>while</code> loop</a> is an intentional infinite loop that terminates only when the user enters <code>quit</code> at the command line.</p>
<p>In the loop, you grab the user query using the built-in <a href="https://realpython.com/python-keyboard-input/"><code>input()</code></a> function, which you store in the <code>query</code> <a href="https://realpython.com/python-variables/">variable</a>. Next, you use this query as an argument to <code>handler.process_query()</code>, which will pass the query to an OpenAI model for further processing. If an error occurs, then you print the corresponding error message.</p>
</section><section class="section3"><h3 id="create-a-chat-handler-with-openais-python-sdk">Create a Chat Handler With OpenAI&rsquo;s Python SDK<a class="headerlink" href="#create-a-chat-handler-with-openais-python-sdk" title="Permanent link"></a></h3>
<p>Interacting with an MCP server requires an AI-powered client. Up to this point, your MCP client doesn&rsquo;t have AI-powered capabilities. In this section, you&rsquo;ll implement that. You&rsquo;ll use OpenAI&rsquo;s Python SDK, which you already installed in your working environment.</p>
<p>Go back to your code editor and create a new file called <code>handlers.py</code> under the <code>mcp_client</code> package. Then, start with the following code:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/handlers.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">json</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">os</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">mcp</span><span class="w"> </span><span class="kn">import</span> <span class="n">ClientSession</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">openai</span><span class="w"> </span><span class="kn">import</span> <span class="n">OpenAI</span>

<span class="n">MODEL</span> <span class="o">=</span> <span class="s2">&quot;gpt-4o-mini&quot;</span>
<span class="n">MAX_TOKENS</span> <span class="o">=</span> <span class="mi">1000</span>

<span class="k">class</span><span class="w"> </span><span class="nc">OpenAIQueryHandler</span><span class="p">:</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;Handle OpenAI API interaction and MCP tool execution.&quot;&quot;&quot;</span>

    <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">client_session</span><span class="p">:</span> <span class="n">ClientSession</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">client_session</span> <span class="o">=</span> <span class="n">client_session</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="n">api_key</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&quot;OPENAI_API_KEY&quot;</span><span class="p">)):</span>
            <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span>
                <span class="s2">&quot;Error: OPENAI_API_KEY environment variable not set&quot;</span><span class="p">,</span>
            <span class="p">)</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">openai</span> <span class="o">=</span> <span class="n">OpenAI</span><span class="p">(</span><span class="n">api_key</span><span class="o">=</span><span class="n">api_key</span><span class="p">)</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>After the required imports, you define two <a href="https://realpython.com/python-constants/">constants</a>, <code>MODEL</code> and <code>MAX_TOKENS</code>, which hold the model you&rsquo;ll use and the maximum number of allowed tokens, as their names suggest.</p>
<p>Next, you define the <code>OpenAIQueryHandler</code> class. The initializer takes a <code>ClientSession</code> as an argument and defines the <code>.client_session</code> and <code>.openai</code> attributes. It also verifies whether an OpenAI API key is set up in your environment.</p>
<p>The heart of the handler is the <code>.process_query()</code> method, which accepts a user query as a string and performs the following actions:</p>
<ul>
<li>Forwarding the query to OpenAI along with the list of available tools on the MCP server</li>
<li>Inspecting the model&rsquo;s response to determine whether tool calls are needed</li>
<li>Triggering any required tool calls</li>
<li>Requesting a final answer from the model that integrates any tool outputs</li>
</ul>
<p>Here&rsquo;s the implementation of your <code>process_query()</code> method:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/handlers.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">OpenAIQueryHandler</span><span class="p">:</span>
    <span class="c1"># ...</span>

    <span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">process_query</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">query</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="w">        </span><span class="sd">&quot;&quot;&quot;Process a query using OpenAI and available MCP tools.&quot;&quot;&quot;</span>
        <span class="c1"># Get initial model&#39;s response and decision on tool calls</span>
        <span class="n">messages</span> <span class="o">=</span> <span class="p">[{</span><span class="s2">&quot;role&quot;</span><span class="p">:</span> <span class="s2">&quot;user&quot;</span><span class="p">,</span> <span class="s2">&quot;content&quot;</span><span class="p">:</span> <span class="n">query</span><span class="p">}]</span>
        <span class="n">initial_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">openai</span><span class="o">.</span><span class="n">chat</span><span class="o">.</span><span class="n">completions</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
            <span class="n">model</span><span class="o">=</span><span class="n">MODEL</span><span class="p">,</span>
            <span class="n">max_tokens</span><span class="o">=</span><span class="n">MAX_TOKENS</span><span class="p">,</span>
            <span class="n">messages</span><span class="o">=</span><span class="n">messages</span><span class="p">,</span>
            <span class="n">tools</span><span class="o">=</span><span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_tools</span><span class="p">(),</span>
        <span class="p">)</span>

        <span class="n">current_message</span> <span class="o">=</span> <span class="n">initial_response</span><span class="o">.</span><span class="n">choices</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">message</span>
        <span class="n">result_parts</span> <span class="o">=</span> <span class="p">[]</span>

        <span class="k">if</span> <span class="n">current_message</span><span class="o">.</span><span class="n">content</span><span class="p">:</span>
            <span class="n">result_parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_message</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>

        <span class="c1"># Handle tool usage if present</span>
        <span class="k">if</span> <span class="n">tool_calls</span> <span class="o">:=</span> <span class="n">current_message</span><span class="o">.</span><span class="n">tool_calls</span><span class="p">:</span>
            <span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
                <span class="p">{</span>
                    <span class="s2">&quot;role&quot;</span><span class="p">:</span> <span class="s2">&quot;assistant&quot;</span><span class="p">,</span>
                    <span class="s2">&quot;content&quot;</span><span class="p">:</span> <span class="n">current_message</span><span class="o">.</span><span class="n">content</span> <span class="ow">or</span> <span class="s2">&quot;&quot;</span><span class="p">,</span>
                    <span class="s2">&quot;tool_calls&quot;</span><span class="p">:</span> <span class="n">tool_calls</span><span class="p">,</span>
                <span class="p">}</span>
            <span class="p">)</span>

            <span class="c1"># Execute tools</span>
            <span class="k">for</span> <span class="n">tool_call</span> <span class="ow">in</span> <span class="n">tool_calls</span><span class="p">:</span>
                <span class="n">tool_result</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_execute_tool</span><span class="p">(</span><span class="n">tool_call</span><span class="p">)</span>
                <span class="n">result_parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">tool_result</span><span class="p">[</span><span class="s2">&quot;log&quot;</span><span class="p">])</span>
                <span class="n">messages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">tool_result</span><span class="p">[</span><span class="s2">&quot;message&quot;</span><span class="p">])</span>

            <span class="c1"># Get final model&#39;s response after tool execution</span>
            <span class="n">final_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">openai</span><span class="o">.</span><span class="n">chat</span><span class="o">.</span><span class="n">completions</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
                <span class="n">model</span><span class="o">=</span><span class="n">MODEL</span><span class="p">,</span>
                <span class="n">max_tokens</span><span class="o">=</span><span class="n">MAX_TOKENS</span><span class="p">,</span>
                <span class="n">messages</span><span class="o">=</span><span class="n">messages</span><span class="p">,</span>
            <span class="p">)</span>

            <span class="k">if</span> <span class="n">content</span> <span class="o">:=</span> <span class="n">final_response</span><span class="o">.</span><span class="n">choices</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">message</span><span class="o">.</span><span class="n">content</span><span class="p">:</span>
                <span class="n">result_parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>

        <span class="k">return</span> <span class="s2">&quot;Assistant: &quot;</span> <span class="o">+</span> <span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">result_parts</span><span class="p">)</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>First, you construct an initial conversation with the OpenAI model using the user&rsquo;s query. You invoke <code>.create()</code>, passing in the model, token limits, messages, and the result of calling <code>._get_tools()</code>, which packages the MCP tools into a schema that OpenAI can process.</p>
<div class="alert alert-primary" role="alert">
<p><strong>Note:</strong> The model you use for this project must support tool calling or function calling. If you use a model or version of OpenAI&rsquo;s API that doesn&rsquo;t support the <code>tools</code> argument, your code will fail.</p>
</div>
<p>The model returns a message object that may include direct content and optionally instructions to call one or more tools on the MCP server.</p>
<p>If the model produces content, then you append it to a list of result parts. Then, you check whether the response requires any tool calls. If so, you append the assistant message with the tool-call metadata to the conversation. Next, you loop through the tools and delegate execution to <code>._execute_tool()</code>.</p>
<p>After executing all tool calls, you make a second call to the model to request a final answer that incorporates the tool outputs. To wrap up the process, you append the final content and return a formatted answer that combines all parts. This will be the AI assistant&rsquo;s response in the chat interface.</p>
<p>The <code>._get_tools()</code> helper is fairly straightforward. Here&rsquo;s its code:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/handlers.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">OpenAIQueryHandler</span><span class="p">:</span>
    <span class="c1"># ...</span>

    <span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_get_tools</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
<span class="w">        </span><span class="sd">&quot;&quot;&quot;Get MCP tools formatted for OpenAI.&quot;&quot;&quot;</span>
        <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">client_session</span><span class="o">.</span><span class="n">list_tools</span><span class="p">()</span>
        <span class="k">return</span> <span class="p">[</span>
            <span class="p">{</span>
                <span class="s2">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;function&quot;</span><span class="p">,</span>
                <span class="s2">&quot;function&quot;</span><span class="p">:</span> <span class="p">{</span>
                    <span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="n">tool</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
                    <span class="s2">&quot;description&quot;</span><span class="p">:</span> <span class="n">tool</span><span class="o">.</span><span class="n">description</span> <span class="ow">or</span> <span class="s2">&quot;No description&quot;</span><span class="p">,</span>
                    <span class="s2">&quot;parameters&quot;</span><span class="p">:</span> <span class="nb">getattr</span><span class="p">(</span>
                        <span class="n">tool</span><span class="p">,</span>
                        <span class="s2">&quot;inputSchema&quot;</span><span class="p">,</span>
                        <span class="p">{</span><span class="s2">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;object&quot;</span><span class="p">,</span> <span class="s2">&quot;properties&quot;</span><span class="p">:</span> <span class="p">{}},</span>
                    <span class="p">),</span>
                <span class="p">},</span>
            <span class="p">}</span>
            <span class="k">for</span> <span class="n">tool</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">tools</span>
        <span class="p">]</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>In this method, you get the MCP server&rsquo;s tools by calling <code>.list_tools()</code>. Then, you transform each tool into an OpenAI-ready <a href="https://realpython.com/python-json/">JSON</a> schema format that describes the function to call. Each tool contains a name, description, and input schema.</p>
<p>This information is crucial because it enables OpenAI models to determine whether to invoke any tools to generate the final response that integrates the resulting data.</p>
<p>Finally, you need the <code>._execute_tool()</code> helper method. Here&rsquo;s its implementation:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/handlers.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">OpenAIQueryHandler</span><span class="p">:</span>
    <span class="c1"># ...</span>

    <span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_execute_tool</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tool_call</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
<span class="w">        </span><span class="sd">&quot;&quot;&quot;Execute an MCP tool call and return formatted result.&quot;&quot;&quot;</span>
        <span class="n">tool_name</span> <span class="o">=</span> <span class="n">tool_call</span><span class="o">.</span><span class="n">function</span><span class="o">.</span><span class="n">name</span>
        <span class="n">tool_args</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">tool_call</span><span class="o">.</span><span class="n">function</span><span class="o">.</span><span class="n">arguments</span> <span class="ow">or</span> <span class="s2">&quot;</span><span class="si">{}</span><span class="s2">&quot;</span><span class="p">)</span>

        <span class="k">try</span><span class="p">:</span>
            <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">client_session</span><span class="o">.</span><span class="n">call_tool</span><span class="p">(</span>
                <span class="n">tool_name</span><span class="p">,</span> <span class="n">tool_args</span><span class="p">,</span>
            <span class="p">)</span>
            <span class="n">content</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="n">content</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">text</span> <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">content</span> <span class="k">else</span> <span class="s2">&quot;&quot;</span>
            <span class="n">log</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;[Used </span><span class="si">{</span><span class="n">tool_name</span><span class="si">}</span><span class="s2">(</span><span class="si">{</span><span class="n">tool_args</span><span class="si">}</span><span class="s2">)]&quot;</span>
        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="n">content</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&quot;</span>
            <span class="n">log</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;[</span><span class="si">{</span><span class="n">content</span><span class="si">}</span><span class="s2">]&quot;</span>

        <span class="k">return</span> <span class="p">{</span>
            <span class="s2">&quot;log&quot;</span><span class="p">:</span> <span class="n">log</span><span class="p">,</span>
            <span class="s2">&quot;message&quot;</span><span class="p">:</span> <span class="p">{</span>
                <span class="s2">&quot;role&quot;</span><span class="p">:</span> <span class="s2">&quot;tool&quot;</span><span class="p">,</span>
                <span class="s2">&quot;tool_call_id&quot;</span><span class="p">:</span> <span class="n">tool_call</span><span class="o">.</span><span class="n">id</span><span class="p">,</span>
                <span class="s2">&quot;content&quot;</span><span class="p">:</span> <span class="n">content</span><span class="p">,</span>
            <span class="p">},</span>
        <span class="p">}</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>In the <code>._execute_tool()</code> helper, you take a <code>tool_call</code> object from the model&rsquo;s output and run it through your <code>ClientSession</code>. To do this, you first extract the tool&rsquo;s name and parse its arguments using the <code>json.loads()</code> function.</p>
<p>Next, you await <code>client_session.call_tool()</code> to execute the current tool on the MCP server side. If the tool returns structured content, then you extract the text of its first item. Otherwise, you set the content to an empty string. You also construct a log string summarizing the invocation to provide user feedback in the chat interface.</p>
<p>If the tool execution fails due to an exception, then you catch the error and create a log accordingly. Finally, you return a <a href="/ref/glossary/dictionary/" class="ref-link">dictionary</a> with two keys: <code>"log"</code> containing the summary string, and <code>"message"</code> containing the tool role message for the AI model.</p>
<p>That&rsquo;s it! You&rsquo;ve coded the AI-powered handler for your client&rsquo;s chat interface. It&rsquo;s time to update the <code>MCPClient</code> class.</p>
<div><div class="rounded border border-light" style="display:block;position:relative;"> <div style="display:block;width:100%;padding-top:12.5%;"></div> <div class="rpad rounded border" data-unit="8x1" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;"> </div></div><a class="small text-muted" href="/account/join/" rel="nofollow"> <span aria-hidden="true" class="icon baseline mr-1"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--circle-info"></use></svg></span>Remove ads</a></div></section><section class="section3"><h3 id="connect-the-chat-with-the-mcp-client">Connect the Chat With the MCP Client<a class="headerlink" href="#connect-the-chat-with-the-mcp-client" title="Permanent link"></a></h3>
<p>Go back to the <code>mcp_client.py</code> file. First, update the imports to add the <code>chat</code> function and the <code>OpenAIQueryHandler</code> class:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/mcp_client.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">sys</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">contextlib</span><span class="w"> </span><span class="kn">import</span> <span class="n">AsyncExitStack</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Awaitable</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">ClassVar</span><span class="p">,</span> <span class="n">Self</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">mcp</span><span class="w"> </span><span class="kn">import</span> <span class="n">ClientSession</span><span class="p">,</span> <span class="n">StdioServerParameters</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">mcp.client.stdio</span><span class="w"> </span><span class="kn">import</span> <span class="n">stdio_client</span>

<span class="hll"><span class="kn">from</span><span class="w"> </span><span class="nn">mcp_client</span><span class="w"> </span><span class="kn">import</span> <span class="n">chat</span>
</span><span class="hll"><span class="kn">from</span><span class="w"> </span><span class="nn">mcp_client.handlers</span><span class="w"> </span><span class="kn">import</span> <span class="n">OpenAIQueryHandler</span>
</span>
<span class="c1"># ...</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>Once you&rsquo;ve updated the imports, you can add the <code>.run_chat()</code> method to <code>MCPClient</code>:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/mcp_client.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="c1"># ...</span>

<span class="k">class</span><span class="w"> </span><span class="nc">MCPClient</span><span class="p">:</span>
    <span class="c1"># ...</span>

    <span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">run_chat</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="w">        </span><span class="sd">&quot;&quot;&quot;Start interactive chat with MCP server using OpenAI.&quot;&quot;&quot;</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">handler</span> <span class="o">=</span> <span class="n">OpenAIQueryHandler</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">client_session</span><span class="p">)</span>
            <span class="k">await</span> <span class="n">chat</span><span class="o">.</span><span class="n">run_chat</span><span class="p">(</span><span class="n">handler</span><span class="p">)</span>
        <span class="k">except</span> <span class="ne">RuntimeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>In the <code>.run_chat()</code> method, you create the AI handler for the chat session and then call the <code>.run_chat()</code> function to start the chat interface. If an error occurs, then you print an appropriate error message to inform the user.</p>
<p>With these updates in place, you can now update the app&rsquo;s entry-point script so that you can use the new chat functionality from the command line.</p>
</section><section class="section3"><h3 id="update-the-clients-entry-point-script_1">Update the Client&rsquo;s Entry-Point Script<a class="headerlink" href="#update-the-clients-entry-point-script_1" title="Permanent link"></a></h3>
<p>To integrate the <code>--chat</code> option into your client&rsquo;s behavior, you just need to make a minimal change to the <code>__main__.py</code> file. Consider the highlighted lines below:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/__main__.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">asyncio</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">mcp_client.cli</span><span class="w"> </span><span class="kn">import</span> <span class="n">parse_args</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">mcp_client.mcp_client</span><span class="w"> </span><span class="kn">import</span> <span class="n">MCPClient</span>

<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;Run the MCP client with the specified options.&quot;&quot;&quot;</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">parse_args</span><span class="p">()</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">args</span><span class="o">.</span><span class="n">server_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Error: Server script &#39;</span><span class="si">{</span><span class="n">args</span><span class="o">.</span><span class="n">server_path</span><span class="si">}</span><span class="s2">&#39; not found&quot;</span><span class="p">)</span>
        <span class="k">return</span>

    <span class="k">try</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">MCPClient</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">server_path</span><span class="p">))</span> <span class="k">as</span> <span class="n">client</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">members</span><span class="p">:</span>
                <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">list_all_members</span><span class="p">()</span>
<span class="hll">            <span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">chat</span><span class="p">:</span>
</span><span class="hll">                <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">run_chat</span><span class="p">()</span>
</span>    <span class="k">except</span> <span class="ne">RuntimeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&quot;__main__&quot;</span><span class="p">:</span>
    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>In this update, you just add an <a href="/ref/keywords/elif/" class="ref-link"><code>elif</code></a> branch to the conditional. This branch checks whether the user provided the <code>--chat</code> command-line option. If that&rsquo;s the case, then you call <code>.run_chat()</code> on the client object.</p>
<p>Now you&rsquo;re all set up to give the chat interface a try. Go ahead, run the following command, and then enter a request for the assistant:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="console" data-is-repl="true">
  <div class="codeblock__header codeblock--yellow">
    <span class="mr-2 noselect" aria-label="Language">Shell</span>
    
    <div class="noselect">
      
        <span class="codeblock__output-toggle" title="Toggle prompts and output" role="button"><span class="icon baseline js-codeblock-output-on codeblock__header--icon-lower" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#regular--rectangle-terminal"/></svg></span></span>
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="gp">$ </span>uv<span class="w"> </span>run<span class="w"> </span>python<span class="w"> </span>-m<span class="w"> </span>mcp_client<span class="w"> </span>mcp_server/mcp_server.py<span class="w"> </span>--chat

<span class="go">MCP Client&#39;s Chat Started!</span>
<span class="go">Type your queries or &#39;quit&#39; to exit.</span>

<span class="hll"><span class="go">You: echo My first MCP client!</span>
</span>
<span class="go">Assistant: [Used echo({&#39;message&#39;: &#39;My first MCP client!&#39;})]</span>
<span class="go">My first MCP client!</span>

<span class="go">You:</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>That looks great! The chat interface works and lets you query the model and receive a response. Notice how the model incorporates the tool-call result into its reply. This confirms that the target MCP server was used correctly. To exit the chat interface, type <code>quit</code> and press <span class="keys"><kbd class="key-enter">Enter</kbd></span>.</p>
</section><section class="section3"><h3 id="make-the-mcp-client-app-installable">Make the MCP Client App Installable<a class="headerlink" href="#make-the-mcp-client-app-installable" title="Permanent link"></a></h3>
<p>In this final section, you&rsquo;ll edit the <code>pyproject.toml</code> file so you can use <code>uv</code> to build and install your MCP client locally with an editable install.</p>
<p>At this point, your project should have the following directory structure:</p>
<div class="highlight"><pre><span></span><code>mcp-client/
├── .git/                    # Git version control
├── .venv/                   # Python virtual environment
├── mcp_client/              # MCP client
│   ├── __init__.py
│   ├── __main__.py
<span class="hll">│   ├── chat.py
</span>│   ├── cli.py
<span class="hll">│   ├── handlers.py
</span>│   └── mcp_client.py
├── mcp_server/              # Test MCP server
│   └── mcp_server.py
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── uv.lock
</code></pre></div>
<p>Go ahead and open <code>pyproject.toml</code> in your code editor. Then, update it as shown below:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="toml" >
  <div class="codeblock__header codeblock--grey">
    <span class="mr-2 noselect" aria-label="Language">TOML</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/pyproject.toml</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="k">[project]</span>
<span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;mcp-client&quot;</span>
<span class="n">version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;0.1.0&quot;</span>
<span class="hll"><span class="n">description</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;MCP client for testing MCP servers.&quot;</span>
</span><span class="n">readme</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;README.md&quot;</span>
<span class="n">requires-python</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;&gt;=3.14&quot;</span>
<span class="n">dependencies</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span>
<span class="w">    </span><span class="s2">&quot;mcp&gt;=1.20.0&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="s2">&quot;openai&gt;=2.6.1&quot;</span><span class="p">,</span>
<span class="p">]</span>

<span class="hll"><span class="k">[project.scripts]</span>
</span><span class="hll"><span class="n">mcp-client</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;mcp_client.__main__:cli_main&quot;</span>
</span>
<span class="hll"><span class="k">[build-system]</span>
</span><span class="hll"><span class="n">requires</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;setuptools&gt;=61.0&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;wheel&quot;</span><span class="p">]</span>
</span><span class="hll"><span class="n">build-backend</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;setuptools.build_meta&quot;</span>
</span>
<span class="hll"><span class="k">[tool.setuptools.packages.find]</span>
</span><span class="hll"><span class="n">include</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;mcp_client*&quot;</span><span class="p">]</span><span class="w">  </span><span class="c1"># Only include mcp_client package</span>
</span><span class="hll"><span class="n">exclude</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;mcp_server*&quot;</span><span class="p">]</span><span class="w">  </span><span class="c1"># Explicitly exclude mcp_server</span>
</span></code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>First, you update the project description. Next, you set up the entry-point script in the <code>[project.scripts]</code> <a href="https://realpython.com/python-toml/#tables">table</a>. Note that you&rsquo;ve set this key to <code>mcp_client.__main__:cli_main</code>, but this function isn&rsquo;t defined in the <code>__main__.py</code> file yet. Add the function just below <code>main()</code>. Here&rsquo;s the code:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="python" >
  <div class="codeblock__header codeblock--blue">
    <span class="mr-2 noselect" aria-label="Language">Python</span>
    <span class="mr-2" aria-label="Filename"><code style="color: inherit; background: inherit;">mcp-client/mcp_client/__main__.py</code></span>
    <div class="noselect">
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="c1"># ...</span>

<span class="k">def</span><span class="w"> </span><span class="nf">cli_main</span><span class="p">():</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;Entry point for the mcp-client CLI app.&quot;&quot;&quot;</span>
    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>

<span class="c1"># ...</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>You need this function because the <code>setuptools</code> build system can&rsquo;t handle async functions like <code>main()</code>. It only supports synchronous callables. A possible solution is to wrap the async <code>main()</code> in a synchronous function, such as <code>cli_main()</code> in this example.</p>
<p>You&rsquo;ll use <code>setuptools</code> as the build system, which is configured in the <code>[build-system]</code> table.</p>
<p>Because this project includes a test MCP server in a top-level folder, you must instruct the build system to exclude this folder from the build. That&rsquo;s what you do in the <code>[tool.setuptools.packages.find]</code> table.</p>
<p>To try this setup out, go ahead and run the following in your terminal:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="console" data-is-repl="true">
  <div class="codeblock__header codeblock--yellow">
    <span class="mr-2 noselect" aria-label="Language">Shell</span>
    
    <div class="noselect">
      
        <span class="codeblock__output-toggle" title="Toggle prompts and output" role="button"><span class="icon baseline js-codeblock-output-on codeblock__header--icon-lower" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#regular--rectangle-terminal"/></svg></span></span>
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="gp">$ </span>uv<span class="w"> </span>sync
<span class="go">Resolved 33 packages in 353ms</span>
<span class="go">      Built mcp-client @ file:///Users/realpython/mcp-client</span>
<span class="go">Prepared 1 package in 551ms</span>
<span class="go">Installed 1 package in 1ms</span>
<span class="go"> + mcp-client==0.1.0 (from file:///Users/realpython/mcp-client)</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>When you run this command, <code>uv</code> sets up everything for you using the updated <code>pyproject.toml</code> file. From now on, you can run your MCP client using the <code>mcp-client</code> command directly once you activate the project&rsquo;s virtual environment:</p>
<code-block class="mb-3" aria-label="Code block" data-syntax-language="console" data-is-repl="true">
  <div class="codeblock__header codeblock--yellow">
    <span class="mr-2 noselect" aria-label="Language">Shell</span>
    
    <div class="noselect">
      
        <span class="codeblock__output-toggle" title="Toggle prompts and output" role="button"><span class="icon baseline js-codeblock-output-on codeblock__header--icon-lower" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#regular--rectangle-terminal"/></svg></span></span>
      
    </div>
  </div>
  <div class="codeblock__contents">
    <div class="highlight highlight--with-header"><pre><span></span><code><span class="gp">$ </span><span class="nb">source</span><span class="w"> </span>.venv/bin/activate
<span class="gp gp-VirtualEnv">(mcp-client)</span> <span class="gp">$ </span>mcp-client<span class="w"> </span>--help
<span class="go">usage: mcp-client [-h] (--members | --chat) server_path</span>

<span class="go">A minimal MCP client</span>

<span class="go">positional arguments:</span>
<span class="go">  server_path           path to the MCP server script</span>

<span class="go">options:</span>
<span class="go">  -h, --help            show this help message and exit</span>
<span class="go">  --members             list the MCP server&#39;s tools, prompts, and resources</span>
<span class="go">  --chat                start an AI-powered chat with MCP server integration</span>
</code></pre></div>
    
    <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@copy"/></svg></span></button>
    
  </div>
</code-block>
<p>As you can see, the <code>mcp-client</code> command now works when used directly in the project&rsquo;s virtual environment.</p>
<p>That&rsquo;s it! You&rsquo;ve built a command-line interface MCP client using the Python SDK for MCP and OpenAI. You can use this client to test your MCP servers during development. However, keep in mind that this is a minimal client that doesn&rsquo;t cover production concerns such as security, concurrency, error handling, tool permissions, and others.</p>
<div><div class="rounded border border-light" style="display:block;position:relative;"> <div style="display:block;width:100%;padding-top:12.5%;"></div> <div class="rpad rounded border" data-unit="8x1" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;"> </div></div><a class="small text-muted" href="/account/join/" rel="nofollow"> <span aria-hidden="true" class="icon baseline mr-1"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--circle-info"></use></svg></span>Remove ads</a></div></section></section><section class="section2"><h2 id="conclusion">Conclusion<a class="headerlink" href="#conclusion" title="Permanent link"></a></h2>
<p>You&rsquo;ve learned how to build a basic command-line Model Context Protocol (MCP) client in Python, which allows you to connect to MCP servers, discover their capabilities, and interact with them through an AI-powered chat interface.</p>
<p>You started by setting up your project environment and implementing a robust client class using asynchronous context management and the MCP Python SDK. Next, you added a user-friendly command-line interface with <code>argparse</code> for seamless capability discovery and interactive chat sessions powered by OpenAI&rsquo;s API. Finally, you packaged your project for installation.</p>
<p>Integrating AI and external tools is a valuable skill for any Python developer working with modern AI workflows that use MCP.</p>
<p><strong>In this tutorial, you&rsquo;ve learned how to:</strong></p>
<ul>
<li>Set up and structure a <strong>Python project</strong> using <code>uv</code> and <code>pyproject.toml</code></li>
<li>Implement an asynchronous <strong>MCP client</strong> with the <strong>MCP Python SDK</strong></li>
<li>Build a robust <strong>command-line interface (CLI)</strong> using <strong><code>argparse</code></strong></li>
<li>List and inspect <strong>MCP server capabilities</strong>, including <strong>tools</strong>, <strong>prompts</strong>, and <strong>resources</strong></li>
<li>Use OpenAI&rsquo;s Python SDK to enable <strong>AI-powered chat</strong> with <strong>tool invocation</strong></li>
</ul>
<p>With these skills, you can now quickly test MCP servers directly from your terminal. You can also extend your client app with additional features as needed. You&rsquo;re now well-equipped to experiment with advanced AI integrations and to streamline your MCP server development cycle.</p>
</section><section class="section2"><h2 id="next-steps">Next Steps<a class="headerlink" href="#next-steps" title="Permanent link"></a></h2>
<p>Now that you&rsquo;ve finished building your MCP client app, you can take the project a step further by adding new functionality. Adding new features will help you continue to learn exciting new coding concepts and techniques.</p>
<p>Here are some ideas to take your project to the next level:</p>
<ul>
<li><strong>Add HTTP streaming support:</strong> Modify the code so the client can also connect to online MCP servers using HTTP POST and GET requests.</li>
<li><strong>Handle prompts and resources:</strong> Add code to support not only tool calling but also MCP prompts and resources.</li>
<li><strong>Support additional AI models:</strong> Enable models from other online and local providers to handle the chat.</li>
</ul>
<p>These features will make your client even more powerful and flexible, allowing you to test a wider range of MCP servers. Refer to the MCP documentation for additional <a href="https://modelcontextprotocol.io/clients">client examples</a>.</p>
<div class="alert alert-warning" role="alert">
<p><strong markdown>Get Your Code:</strong> <a href="https://realpython.com/bonus/python-mcp-client-code/" class="alert-link" data-toggle="modal" data-target="#modal-python-mcp-client-code" markdown>Click here to download the free sample code</a> you&rsquo;ll use to build a Python MCP client to test servers from your terminal.</p>
</div>
</section><section class="section2"><h2 id="frequently-asked-questions">Frequently Asked Questions<a class="headerlink" href="#frequently-asked-questions" title="Permanent link"></a></h2>
<p>Now that you have some experience with building an MCP client in Python, you can use the questions and answers below to check your understanding and recap what you&rsquo;ve learned.</p>
<p>These FAQs are related to the most important concepts you&rsquo;ve covered in this tutorial. Click the <em>Show/Hide</em> toggle beside each question to reveal the answer.</p>
<div class="card mb-3" id="collapse_cardb85822">
<div class="card-header border-0">
<p class="m-0">
    <button class="btn w-100" data-toggle="collapse" data-target="#collapseb85822" aria-expanded="false" aria-controls="collapseb85822" markdown="1"><span class="float-left" markdown="1"><strong>How do you connect a Python MCP client to a server over stdio?</strong></span><span class="float-right text-muted">Show/Hide</span></button>
  </p>
</div>
<div class="collapse js-collapsible-section" data-parent="#collapse_cardb85822" id="collapseb85822">
<div class="card-body">
<p>You create a client by calling <code>stdio_client()</code> with <code>StdioServerParameters</code> and wrapping the input and output streams in a <code>ClientSession</code> instance.</p>
</div>
</div>
</div>
<div class="card mb-3" id="collapse_card4fdd0c">
<div class="card-header border-0">
<p class="m-0">
    <button class="btn w-100" data-toggle="collapse" data-target="#collapse4fdd0c" aria-expanded="false" aria-controls="collapse4fdd0c" markdown="1"><span class="float-left" markdown="1"><strong>How do you list an MCP server’s tools, prompts, and resources?</strong></span><span class="float-right text-muted">Show/Hide</span></button>
  </p>
</div>
<div class="collapse js-collapsible-section" data-parent="#collapse_card4fdd0c" id="collapse4fdd0c">
<div class="card-body">
<p>You call <code>.list_tools()</code>, <code>.list_prompts()</code>, and <code>.list_resources()</code> on a <code>ClientSession</code> instance. You can read each item&rsquo;s <code>.name</code> and <code>.description</code> attributes to get a more detailed summary.</p>
</div>
</div>
</div>
<div class="card mb-3" id="collapse_card955402">
<div class="card-header border-0">
<p class="m-0">
    <button class="btn w-100" data-toggle="collapse" data-target="#collapse955402" aria-expanded="false" aria-controls="collapse955402" markdown="1"><span class="float-left" markdown="1"><strong>Why does the client use an async context manager with <code>AsyncExitStack</code>?</strong></span><span class="float-right text-muted">Show/Hide</span></button>
  </p>
</div>
<div class="collapse js-collapsible-section" data-parent="#collapse_card955402" id="collapse955402">
<div class="card-body">
<p>The client uses an async context manager to safely manage the stdio transport and session lifetime by entering and exiting in one place. <code>AsyncExitStack</code> lets you acquire the read and write streams, attach <code>ClientSession()</code>, and guarantee cleanup even if a step fails.</p>
</div>
</div>
</div>
<div class="container border rounded text-wrap-pretty my-3">

  <p class="my-3"><mark class="marker-highlight"><strong><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@quiz"/></svg></span> Take the Quiz:</strong></mark> Test your knowledge with our interactive “Build a Python MCP Client to Test Servers From Your Terminal” quiz. You’ll receive a score upon completion to help you track your learning progress:</p>

  <hr>

  <div class="row my-3">
    <div class="col-xs-12 col-sm-4 col-md-3 align-self-center">

      <a href="/quizzes/python-mcp-client/" tabindex="-1">
        <div class="embed-responsive embed-responsive-16by9">

            <img class="card-img-top m-0 p-0 embed-responsive-item rounded" style="object-fit: contain; background: #ffc973;" alt="Build a Python MCP Client to Test Servers From Your Terminal" src="https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg" width="1920" height="1080" srcset="/cdn-cgi/image/width=480,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 480w, /cdn-cgi/image/width=640,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 640w, /cdn-cgi/image/width=960,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 960w, /cdn-cgi/image/width=1920,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 1920w" sizes="(min-width: 1200px) 142px, (min-width: 1000px) 122px, (min-width: 780px) 112px, (min-width: 580px) 139px, calc(100vw - 62px)">


          <div class="card-img-overlay d-flex align-items-center">
            <div class="mx-auto">
              <span class="text-light" style="opacity: 0.90;"><span class="icon baseline scale2x" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@quiz"/></svg></span></span>
            </div>
          </div>
        </div>
      </a>

    </div>
    <div class="col">
      <div class="mt-3 d-md-none"></div> 
      <p class="small text-muted mb-0"><strong>Interactive Quiz</strong></p>
      <a href="/quizzes/python-mcp-client/" class="stretched-link"><span class="my-0 h4">Build a Python MCP Client to Test Servers From Your Terminal</span></a> 
      <p class="text-muted mb-0 small">Learn how to create a Python MCP client, start an AI-powered chat session, and run it from the command line. Check your understanding.</p>
    </div>
  </div>

</div>

</section>

    
      
      <div class="d-flex justify-content-center flex-wrap gap-2 my-3">
        


<completion-status
  class="btn-group mb-0"
  article-slug="python-mcp-client"
  
  
  
  data-api-article-bookmark-url="/api/v1/articles/python-mcp-client/bookmark/"
  data-api-article-completion-status-url="/api/v1/articles/python-mcp-client/completion_status/">
  <button
    data-target-complete-button
    title="Click to mark as completed"
    class="btn btn-secondary border-right"
    style="border-top-right-radius: 0; border-bottom-right-radius: 0;"
    disabled>Mark as Completed</button>
  <button
    data-target-bookmark-button
    title="Add bookmark"
    class="btn btn-secondary border-left"
    disabled><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#light--bookmark"/></svg></span></button>
</completion-status>

        



<div class="btn-group mb-0">
  <a class="btn btn-secondary border-right" style="border-top-right-radius: 0; border-bottom-right-radius: 0;" title="Liked it" role="button" aria-label="Liked it (thumbs up)" href="/feedback/survey/article/python-mcp-client/liked/?from=article-footer" target="_blank"><span class="icon" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#light--thumbs-up"/></svg></span></a> 
  <a class="btn btn-secondary border-left" role="button" aria-label="Disliked it (thumbs down)" title="Disliked it" href="/feedback/survey/article/python-mcp-client/disliked/?from=article-footer" target="_blank"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#light--thumbs-down"/></svg></span></a>
</div>

        









<share-button
  url="https://realpython.com/python-mcp-client/"
  url-title="Build a Python MCP Client to Test Servers From Your Terminal"
  twitter-text="Interesting #Python article by @realpython:"
  bluesky-text="Interesting #Python article by @realpython.com:"
  email-subject="Python article for you"
  email-body="Check out this Python article:%0A%0ABuild a Python MCP Client to Test Servers From Your Terminal"
>
  <button class="btn btn-secondary" type="button" title="Share" aria-label="Share"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#@share-light"/></svg></span>Share</button>
</share-button>


      </div>
    

    
  </div>

  
    

<div class="card mt-4 mb-4 bg-secondary">
  <p class="card-header h3 text-center bg-light">🐍 Python Tricks 💌</p>
  <div class="card-body">
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-sm-7">
          <p>Get a short &amp; sweet <strong>Python Trick</strong> delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.</p>
        </div>
        <div class="col-xs-12 col-sm-5">
          <img loading="lazy" class="img-fluid rounded mb-3" src="/static/pytrick-dict-merge.4201a0125a5e.png" width="738" height="490" alt="Python Tricks Dictionary Merge">
        </div>
      </div>
      <div class="row mb-3">
        <form class="col-12" action="/optins/process/" method="post">
          <input type="hidden" name="csrfmiddlewaretoken" value="4mTPzdUJcZ3q6Cza0MFKlaiFbn5VQMnrDmwPPTFgWLG5uBvkIMHgDS6MfDYjw832">
          <input type="hidden" name="slug" value="static-python-tricks-footer">
          <input type="hidden" name="source_path" value="/python-mcp-client/">
          <div class="form-group">
            <input name="email" type="email" class="form-control form-control-lg" placeholder="Email Address" required>
          </div>
          <button name="submit" type="submit" class="btn btn-primary btn-block">Send Me Python Tricks »</button>
        </form>
      </div>
    </div>
  </div>
</div>


  

  



<div class="card mt-3" id="author">
  <p class="card-header h3">About <strong>Leodanis Pozo Ramos</strong></p>
  <div class="card-body">
    <div class="container p-0">
      <div class="row">
        <div class="col-12 col-md-3 align-self-center">
          <a href="/team/lpozoramos/" aria-hidden="true" tabindex="-1">
            <img loading="lazy" src="/cdn-cgi/image/width=862,height=862,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg" srcset="/cdn-cgi/image/width=215,height=215,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg 215w, /cdn-cgi/image/width=287,height=287,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg 287w, /cdn-cgi/image/width=431,height=431,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg 431w, /cdn-cgi/image/width=862,height=862,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg 862w" sizes="(min-width: 580px) 154px, calc(33.08vw - 24px)" width="862" height="862" style="background: #534032;" class="d-block d-md-none rounded-circle img-fluid w-33 mb-0 mx-auto" alt="Leodanis Pozo Ramos">
            <img loading="lazy" src="/cdn-cgi/image/width=862,height=862,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg" srcset="/cdn-cgi/image/width=215,height=215,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg 215w, /cdn-cgi/image/width=287,height=287,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg 287w, /cdn-cgi/image/width=431,height=431,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg 431w, /cdn-cgi/image/width=862,height=862,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg 862w" sizes="(min-width: 1200px) 140px, calc(-1.5vw + 137px)" width="862" height="862" style="background: #534032;" class="d-none d-md-block rounded-circle img-fluid w-100 mb-0" alt="Leodanis Pozo Ramos">
          </a>
        </div>
        <div class="col mt-3">
          <p>Leodanis is a self-taught Python developer, educator, and technical writer with over 10 years of experience.</p>
          <a href="/team/lpozoramos/" class="card-link">» More about Leodanis</a>
        </div>
      </div>
    </div>
  </div>
  
  <hr class="my-0">
  <div class="card-body pb-0">
    <div class="container">
      <div class="row">
        <p><em>Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:</em></p>
      </div>

      
        
          <div class="row align-items-center w-100 mx-auto">
        

        <div class="col-4 col-sm-2 align-self-center">
          
            <a href="/team/asantos/" aria-hidden="true" tabindex="-1" data-previewable><img loading="lazy" src="/cdn-cgi/image/width=500,height=500,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Aldren_Santos_Real_Python.6b0861d8b841.png" srcset="/cdn-cgi/image/width=125,height=125,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Aldren_Santos_Real_Python.6b0861d8b841.png 125w, /cdn-cgi/image/width=166,height=166,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Aldren_Santos_Real_Python.6b0861d8b841.png 166w, /cdn-cgi/image/width=250,height=250,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Aldren_Santos_Real_Python.6b0861d8b841.png 250w, /cdn-cgi/image/width=500,height=500,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Aldren_Santos_Real_Python.6b0861d8b841.png 500w" sizes="(min-width: 1200px) 73px, (min-width: 780px) calc(-0.75vw + 69px), (min-width: 580px) 43px, calc(33.46vw - 64px)" width="500" height="500" style="background: #d2cec3;" class="rounded-circle img-fluid w-100" alt="Aldren Santos"></a>
          
        </div>
        <div class="col pl-0 d-none d-sm-block">
          <a href="/team/asantos/" class="card-link small" data-previewable><p>Aldren</p></a>
        </div>

        

        
      
        

        <div class="col-4 col-sm-2 align-self-center">
          
            <a href="/team/bweleschuk/" aria-hidden="true" tabindex="-1" data-previewable><img loading="lazy" src="/cdn-cgi/image/width=320,height=320,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/IMG_3324_1.50b309355fc1.jpg" srcset="/cdn-cgi/image/width=80,height=80,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/IMG_3324_1.50b309355fc1.jpg 80w, /cdn-cgi/image/width=106,height=106,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/IMG_3324_1.50b309355fc1.jpg 106w, /cdn-cgi/image/width=160,height=160,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/IMG_3324_1.50b309355fc1.jpg 160w, /cdn-cgi/image/width=320,height=320,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/IMG_3324_1.50b309355fc1.jpg 320w" sizes="(min-width: 1200px) 73px, (min-width: 780px) calc(-0.75vw + 69px), (min-width: 580px) 43px, calc(33.46vw - 64px)" width="320" height="320" style="background: #d6d4ad;" class="rounded-circle img-fluid w-100" alt="Brenda Weleschuk"></a>
          
        </div>
        <div class="col pl-0 d-none d-sm-block">
          <a href="/team/bweleschuk/" class="card-link small" data-previewable><p>Brenda</p></a>
        </div>

        

        
      
        

        <div class="col-4 col-sm-2 align-self-center">
          
            <a href="/team/bzaczynski/" aria-hidden="true" tabindex="-1" data-previewable><img loading="lazy" src="/cdn-cgi/image/width=1694,height=1694,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/coders_lab_2109368.259b1599fbee.jpg" srcset="/cdn-cgi/image/width=423,height=423,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/coders_lab_2109368.259b1599fbee.jpg 423w, /cdn-cgi/image/width=564,height=564,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/coders_lab_2109368.259b1599fbee.jpg 564w, /cdn-cgi/image/width=847,height=847,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/coders_lab_2109368.259b1599fbee.jpg 847w, /cdn-cgi/image/width=1694,height=1694,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/coders_lab_2109368.259b1599fbee.jpg 1694w" sizes="(min-width: 1200px) 73px, (min-width: 780px) calc(-0.75vw + 69px), (min-width: 580px) 43px, calc(33.46vw - 64px)" width="1694" height="1694" style="background: #dadada;" class="rounded-circle img-fluid w-100" alt="Bartosz Zaczyński"></a>
          
        </div>
        <div class="col pl-0 d-none d-sm-block">
          <a href="/team/bzaczynski/" class="card-link small" data-previewable><p>Bartosz</p></a>
        </div>

        

        
          </div>
        
      
        
          <div class="row align-items-center w-100 mx-auto">
        

        <div class="col-4 col-sm-2 align-self-center">
          
            <a href="/team/mbreuss/" aria-hidden="true" tabindex="-1" data-previewable><img loading="lazy" src="/cdn-cgi/image/width=456,height=456,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/martin_breuss_python_square.efb2b07faf9f.jpg" srcset="/cdn-cgi/image/width=114,height=114,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/martin_breuss_python_square.efb2b07faf9f.jpg 114w, /cdn-cgi/image/width=152,height=152,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/martin_breuss_python_square.efb2b07faf9f.jpg 152w, /cdn-cgi/image/width=228,height=228,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/martin_breuss_python_square.efb2b07faf9f.jpg 228w, /cdn-cgi/image/width=456,height=456,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/martin_breuss_python_square.efb2b07faf9f.jpg 456w" sizes="(min-width: 1200px) 73px, (min-width: 780px) calc(-0.75vw + 69px), (min-width: 580px) 43px, calc(33.46vw - 64px)" width="456" height="456" style="background: #f1f8fe;" class="rounded-circle img-fluid w-100" alt="Martin Breuss"></a>
          
        </div>
        <div class="col pl-0 d-none d-sm-block">
          <a href="/team/mbreuss/" class="card-link small" data-previewable><p>Martin</p></a>
        </div>

        
          
          
            <div class="col-4 col-sm-2 align-self-center"></div>
            <div class="col pl-0 d-none d-sm-block"></div>
            <div class="col-4 col-sm-2 align-self-center"></div>
            <div class="col pl-0 d-none d-sm-block"></div>
          
        

        
          </div>
        
      
    </div>
  </div>
  

</div>





  
    

<div class="bg-light rounded py-4 my-4 shadow shadow-sm mx-n2">
  
  <div class="col-12 text-center d-block d-md-none">
    <p class="h2 mb-3">Master <u><span class="marker-highlight">Real-World Python Skills</mark></u> With Unlimited Access to Real&nbsp;Python</p>
    <p class="mb-1"><img loading="lazy" class="w-75" src="/static/videos/lesson-locked.f5105cfd26db.svg" width="510" height="260" alt="Locked learning resources"></p>
    <p class="mx-auto w-75 mb-3 small"><strong>Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert&nbsp;Pythonistas:</strong></p>
    <p class="mb-0"><a href="/account/join/?utm_source=rp_article_footer&utm_content=python-mcp-client" class="btn btn-primary px-4 mb-0">Level Up Your Python Skills »</a>
  </div>

  
  <div class="col-12 text-center d-none d-md-block">
    <p class="h2 mb-2">Master <u><span class="marker-highlight">Real-World Python Skills</span></u><br>With Unlimited Access to Real&nbsp;Python</p>
    <p class="mb-2"><img loading="lazy" class="w-50 mb-2" src="/static/videos/lesson-locked.f5105cfd26db.svg" width="510" height="260" alt="Locked learning resources"></p>
    <p class="mx-auto w-50 mb-3"><strong>Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:</strong></p>
    <p><a href="/account/join/?utm_source=rp_article_footer&utm_content=python-mcp-client" class="btn btn-primary px-4">Level Up Your Python Skills »</a>
  </div>
</div>

  

  
  <div class="card mt-4" id="reader-comments">
    <p class="card-header h3">What Do You Think?</p>
    
    <div class="text-center mt-3 mb-0 p-0">
      <div class="mb-2">
        <strong class="mr-2">Rate this article:</strong>
        



<div class="btn-group mb-0">
  <a class="btn btn-secondary border-right" style="border-top-right-radius: 0; border-bottom-right-radius: 0;" title="Liked it" role="button" aria-label="Liked it (thumbs up)" href="/feedback/survey/article/python-mcp-client/liked/?from=article-comments" target="_blank"><span class="icon" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#light--thumbs-up"/></svg></span></a> 
  <a class="btn btn-secondary border-left" role="button" aria-label="Disliked it (thumbs down)" title="Disliked it" href="/feedback/survey/article/python-mcp-client/disliked/?from=article-comments" target="_blank"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#light--thumbs-down"/></svg></span></a>
</div>

      </div>
      



<div class="d-flex justify-content-center flex-wrap gap-2 my-3">
  
  
  <a target="_blank" rel="nofollow" href="https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Frealpython.com%2Fpython-mcp-client%2F" class="btn btn-sm share-linkedin" aria-label="Share on LinkedIn"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#brands--linkedin"/></svg></span>LinkedIn</a>
  
  
  <a target="_blank" rel="nofollow" href="https://twitter.com/intent/tweet/?text=Interesting%20Python%20article%20on%20%40realpython%3A%20Build%20a%20Python%20MCP%20Client%20to%20Test%20Servers%20From%20Your%20Terminal&url=https%3A%2F%2Frealpython.com%2Fpython-mcp-client%2F" class="btn btn-sm share-x-twitter" aria-label="Share on X/Twitter"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#brands--x-twitter"/></svg></span>Twitter</a>
  
  
  <a target="_blank" rel="nofollow" href="https://bsky.app/intent/compose?text=Interesting%20Python%20article%20on%20%40realpython.com%3A%20Build%20a%20Python%20MCP%20Client%20to%20Test%20Servers%20From%20Your%20Terminal%20https%3A%2F%2Frealpython.com%2Fpython-mcp-client%2F" class="btn btn-sm share-bluesky" aria-label="Share on Bluesky"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#brands--bluesky"/></svg></span>Bluesky</a>
  
  
  <a target="_blank" rel="nofollow" href="https://facebook.com/sharer/sharer.php?u=https%3A%2F%2Frealpython.com%2Fpython-mcp-client%2F" class="btn btn-sm share-facebook" aria-label="Share on Facebook"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#brands--facebook"/></svg></span>Facebook</a>
  
  
  <a target="_blank" rel="nofollow" href="mailto:?subject=Python%20article%20for%20you&body=Build%20a%20Python%20MCP%20Client%20to%20Test%20Servers%20From%20Your%20Terminal%20on%20Real%20Python%0A%0Ahttps%3A%2F%2Frealpython.com%2Fpython-mcp-client%2F%0A" class="btn btn-sm share-email" aria-label="Share via Email"><span class="icon baseline mr-1" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--envelope"/></svg></span>Email</a>
  
  
</div>

    </div>
    
    <div class="card-body pt-0 text-wrap-pretty">
      <p>What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.</p>

      <div class="alert alert-dark">
        <p class="mb-0"><strong>Commenting Tips:</strong> The most useful comments are those written with the goal of learning from or helping out other students. <a href="https://realpython.com/python-beginner-tips/#tip-9-ask-good-questions" target="_blank">Get tips for asking good questions</a> and <a href="https://support.realpython.com" target="_blank">get answers to common questions in our support portal</a>.<hr>Looking for a real-time conversation? Visit the <a href="/community/" target="_blank">Real Python Community Chat</a> or join the next <a href="/office-hours/" target="_blank">&ldquo;Office&nbsp;Hours&rdquo; Live Q&A Session</a>. Happy Pythoning!</p>

      </div>

      <div class="mb-4" id="disqus_thread">
      
      </div>
      
    </div>
  </div>

  <div class="card mt-4 mb-4">
    <p class="card-header h3">Keep Learning</p>
    <div class="card-body">

      <p class="mb-0">Related Topics:
      
        
<a href="/tutorials/intermediate/" class="badge badge-light text-muted" data-previewable>intermediate</a>

      
        
<a href="/tutorials/ai/" class="badge badge-light text-muted" data-previewable>ai</a>

      
        
<a href="/tutorials/projects/" class="badge badge-light text-muted" data-previewable>projects</a>

      
      </p>

      

      
        <p class="mt-3 mb-0">Related Tutorials:</p>
        <ul class="mb-0">
          
            <li>
              <a href="/python-mcp/?utm_source=realpython&amp;utm_medium=web&amp;utm_campaign=related-post&amp;utm_content=python-mcp-client" data-previewable>Python MCP Server: Connect LLMs to Your Data</a>
            </li>
          
            <li>
              <a href="/python-uv/?utm_source=realpython&amp;utm_medium=web&amp;utm_campaign=related-post&amp;utm_content=python-mcp-client" data-previewable>Managing Python Projects With uv: An All-in-One Solution</a>
            </li>
          
            <li>
              <a href="/fastapi-jinja2-template/?utm_source=realpython&amp;utm_medium=web&amp;utm_campaign=related-post&amp;utm_content=python-mcp-client" data-previewable>How to Serve a Website With FastAPI Using HTML and Jinja2</a>
            </li>
          
            <li>
              <a href="/python-repl/?utm_source=realpython&amp;utm_medium=web&amp;utm_campaign=related-post&amp;utm_content=python-mcp-client" data-previewable>The Python Standard REPL: Try Out Code and Ideas Quickly</a>
            </li>
          
            <li>
              <a href="/python-break/?utm_source=realpython&amp;utm_medium=web&amp;utm_campaign=related-post&amp;utm_content=python-mcp-client" data-previewable>How to Exit Loops Early With the Python break Keyword</a>
            </li>
          
        </ul>
      

    </div>
  </div>

  
    

<div class="modal fade" tabindex="-1" role="dialog" id="rprw">
  <div class="modal-dialog modal-lg modal-dialog-centered" role="document">
    <div class="modal-content">

      <div class="modal-header border-0">
        <div class="col-12 col-lg-9 mx-auto modal-title text-center my-2">
          <h2 class="my-0">Keep reading Real&nbsp;Python by creating a free account or signing&nbsp;in:</h2>
        </div>
      </div>

      
      <div class="modal-body bg-light">
        <div class="col-12 text-center">

          <div class="col-12 col-lg-8 mx-auto mb-2 mt-3">
            <a href="/account/signup/?intent=continue_reading&utm_source=rp&utm_medium=web&utm_campaign=rwn&utm_content=v1&next=%2Fpython-mcp-client%2F"><img loading="lazy" class="w-100" src="/static/videos/lesson-locked.f5105cfd26db.svg"  width="510" height="260" alt="Keep reading"></a>
          </div>

          <p><a href="/account/signup/?intent=continue_reading&utm_source=rp&utm_medium=web&utm_campaign=rwn&utm_content=v1&next=%2Fpython-mcp-client%2F" class="btn btn-primary px-5"></i>Continue »</a></p>

        </div>
      </div>
      

      <div class="modal-footer border-0">
        <p class="text-center text-muted mt-2 mb-1">Already have an account? <a href="/account/login/?next=/python-mcp-client/">Sign-In</a></p>
      </div>

    </div>
  </div>
</div>

    <script src="/static/frontend/reader/rw.c76a15ca70a1.js" async></script>
  


    </div>

  </div>

    </div>

    
    

    
    
      
        
          <div class="modal fade" id="modal-python-mcp-client-code" tabindex="-1" aria-labelledby="modal-python-mcp-client-code-title" aria-describedby="modal-python-mcp-client-code-desc">
  <div class="modal-dialog modal-dialog-centered modal-lg">
    <div class="modal-content">

      <div class="modal-header bg-light pt-3 pb-2">
        <div class="container-fluid">
          <div class="row">
            <div class="col-12">
              <div class="progress" style="height: .5rem;" aria-hidden="true">
                <div class="progress-bar progress-bar-striped progress-bar-animated w-75" role="progressbar"></div>
              </div>
            </div>
            <div class="col-12">
              <p id="modal-python-mcp-client-code-desc" class="text-muted text-center mb-0 mt-2">Almost there! Complete this form and click the button below to gain instant&nbsp;access:</p>
            </div>
          </div>
        </div>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>

      <div class="modal-body m-4">
        <div class="container-fluid">
          <div class="row align-items-center text-center">
            <div class="col-12 col-lg-4 mb-4 mb-lg-0">
              
              
                <img loading="lazy" class="img-fluid rounded" src="https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg" width="1920" height="1080" srcset="/cdn-cgi/image/width=480,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 480w, /cdn-cgi/image/width=640,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 640w, /cdn-cgi/image/width=960,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 960w, /cdn-cgi/image/width=1920,format=auto/https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg 1920w" sizes="(min-width: 1000px) 207px, (min-width: 580px) 382px, calc(100vw - 134px)" alt="Build a Python MCP Client to Test Servers From Your Terminal">
              
            </div>
            <div class="col">
              <p id="modal-python-mcp-client-code-title" class="text-center h3 mb-4">Build a Python MCP Client to Test Servers From Your Terminal (Sample Code)</p>

              <form class="col-12" action="/optins/process/" method="post">
                <input type="hidden" name="csrfmiddlewaretoken" value="4mTPzdUJcZ3q6Cza0MFKlaiFbn5VQMnrDmwPPTFgWLG5uBvkIMHgDS6MfDYjw832">
                <input type="hidden" name="slug" value="python-mcp-client-code">
                <input type="hidden" name="source_path" value="/python-mcp-client/"> 
                <div class="form-group">
                  <input type="email" name="email" class="form-control" placeholder="Email Address" required autofocus>
                </div>
                <button name="submit" type="submit" class="btn btn-primary btn-block text-wrap">Send Code »</button>
                <p class="mb-0 mt-2 text-muted text-center"><small>🔒 No spam. We take your privacy seriously.</small></p>
              </form>

            </div>
          </div>
        </div>
      </div>

    </div>
  </div>
</div>

        
      
    

    
      

<footer class="footer pb-4">
  <div class="mx-5">

    <div class="row justify-content-lg-center">

      <div class="mx-4 mt-5">
        <h5>Learn Python</h5>
        <ul class="list-unstyled mb-0">
          <li><a href="/start-here/">Start Here</a></li>
          <li><a href="/search">Learning Resources</a></li>
          <li><a href="/mentor/">Code Mentor</a></li>
          <li><a href="/ref/">Python Reference</a></li>
          <li><a href="/cheatsheets/python/">Python Cheat Sheet</a></li>
          <li><a href="https://support.realpython.com/">Support Center</a></li>
        </ul>
      </div>

      <div class="mx-4 mt-5">
        <h5>Courses &amp; Paths</h5>
        <ul class="list-unstyled mb-0">
          <li><a href="/learning-paths/">Learning Paths</a></li>
          <li><a href="/quizzes/">Quizzes &amp; Exercises</a></li>
          <li><a href="/tutorials/all/">Browse Topics</a></li>
          <li><a href="/live/">Live Courses</a></li>
          <li><a href="/books/">Books</a></li>
        </ul>
      </div>

      <div class="mx-4 mt-5">
        <h5>Community</h5>
        <ul class="list-unstyled mb-0">
          <li><a href="/podcasts/rpp/">Podcast</a></li>
          <li><a href="/newsletter/">Newsletter</a></li>
          <li><a href="/community/">Community Chat</a></li>
          <li><a href="/office-hours/">Office Hours</a></li>
          <li><a href="/learner-stories/">Learner Stories</a></li>
        </ul>
      </div>

      <div class="mx-4 mt-5">
        <h5>Membership</h5>
        <ul class="list-unstyled mb-0">
          
            <li><a href="/account/join/">Plans &amp; Pricing</a></li>
          
          <li><a href="/account/join-team/">Team Plans</a></li>
          <li><a href="/account/join-team/inquiry/">For Business</a></li>
          <li><a href="/account/join-team/education-inquiry/">For Schools</a></li>
          <li><a href="/learner-stories/">Reviews</a></li>
        </ul>
      </div>

      <div class="mx-4 mt-5">
        <h5>Company</h5>
        <ul class="list-unstyled mb-0">
          <li><a href="/about/">About Us</a></li>
          <li><a href="/team/">Team</a></li>
          <li><a href="/mission/">Mission &amp; Values</a></li>
          <li><a href="/editorial-guidelines/">Editorial Guidelines</a></li>
          <li><a href="/sponsorships/">Sponsorships</a></li>
          <li><a href="https://realpython.workable.com">Careers</a></li>
          <li><a href="/media-kit/">Press Kit</a></li>
          <li><a href="/merch">Merch</a></li>
        </ul>
      </div>

    </div>

    <div class="flex-column flex-lg-row justify-content-center align-items-center mt-5 row ">

      <div class="d-flex justify-content-center mx-4 text-muted flex-nowrap">
        <a class="highlight-icon-hover-dark px-2 ml-n2" title="YouTube" href="https://www.youtube.com/realpython"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#brands--youtube"/></svg></span></a>
        <a class="highlight-icon-hover-dark px-2" title="X/Twitter" href="https://x.com/realpython"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#brands--x-twitter"/></svg></span></a>
        <a class="highlight-icon-hover-dark px-2" title="LinkedIn" href="https://www.linkedin.com/company/realpython-com"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#brands--linkedin"/></svg></span></a>
        <a class="highlight-icon-hover-dark px-2" title="Facebook" href="https://www.facebook.com/LearnRealPython"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#brands--facebook"/></svg></span></a>
        <a class="highlight-icon-hover-dark px-2 mr-n2" title="GitHub" href="https://github.com/realpython/"><span class="icon baseline" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#brands--github"/></svg></span></a>
      </div>

      <div class="mx-4 small text-muted text-center text-wrap-balance">
        <a href="/privacy-policy/">Privacy Policy</a>&nbsp;⋅
        <a href="/terms/">Terms of Use</a>&nbsp;⋅
        
        <a href="/security/">Security</a>&nbsp;⋅
        
        <a href="/contact/">Contact</a>
      </div>

      <div class="mx-4 small text-muted user-select-none text-center">
        <span><span class="icon baseline text-color-red" aria-hidden="true"><svg aria-hidden="true"><use href="/static/icons.0fae35ff985f.svg#solid--heart"/></svg></span> Happy Pythoning!</span>
      </div>

    </div>

    <div class="d-flex justify-content-center small mt-4">
      <div class="text-muted text-center text-wrap-balance" style="line-height: 1.25;">
        <small>
          © 2012–2025 DevCademy Media Inc. DBA Real&nbsp;Python. All rights reserved.<br>
          REALPYTHON™ is a trademark of DevCademy&nbsp;Media&nbsp;Inc.
        </small>
        <a href="/" class="d-flex justify-content-center mt-3 mx-auto w-50">
          <img loading="lazy" src="/static/real-python-logo-primary.973743b6d39d.svg" class="img-fluid flex-grow-1" width="120" height="47" alt="Real Python - Online Python Training (logo)">
        </a>
      </div>
    </div>

  </div>
</footer>

    

    

    
    <script>
      (function() {
        var isAndroid = navigator.userAgent.toLowerCase().indexOf("android") > -1;
        if (!isAndroid) {
          return;
        }

        var styles = `
        @font-face {
          font-family: 'DejaVu Sans Mono';
          font-weight: normal;
          font-style: normal;
          font-display: swap;
          src: url('/static/mfonts/dejavu-sans-mono.33f00225f915.woff2') format('woff2'),
               url('/static/mfonts/dejavu-sans-mono.0da77d3739f3.woff') format('woff'),
               url('/static/mfonts/dejavu-sans-mono.c2356fc49835.ttf') format('truetype');
        }
        code, kbd, pre, samp {
          font-family: 'DejaVu Sans Mono', monospace;
        }
        `

        var styleSheet = document.createElement("style")
        styleSheet.type = "text/css"
        styleSheet.innerText = styles
        document.head.appendChild(styleSheet)
      })();
    </script>

    
    
    <script src="/static/jquery.min.2c872dbe60f4.js"></script>
    <script src="/static/popper.min.aeebb49d0b64.js"></script>
    <script src="/static/bootstrap.min.248284cdd069.js"></script>

    
    


    <script>
    (function() {
      document.querySelectorAll(".js-search-form-submit").forEach(function(el) {
        el.addEventListener("click", function(e) {
          e.preventDefault();
          e.currentTarget.parentElement.submit();
        })
      });
    })();
    </script>
    <script src="/static/frontend/reader/lightbox.e28fb80dcb63.js" async></script>
    <script src="/static/frontend/reader/platforms-ui.c213fbbde909.js" async></script>
    

    <script src="/static/frontend/previews/popover.b23ce52bc0be.js" async></script>

    
      


<exit-prompt>
  <template>
    <div class="card bg-secondary">
      <div class="card-header h3 text-center bg-light">
        Free Bonus: <strong>Python Cheat Sheet</strong>
        <button type="button" class="float-right border-0 bg-transparent text-muted" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
      </div>
      <div class="card-body">
        <p class="mb-0">Get a <strong>Python Cheat Sheet (PDF)</strong> and learn the basics of Python, like working with data types, dictionaries, lists, and Python functions:</p>
        <img class="img-fluid mb-3 w-100 px-3" src="/static/cheatsheet-stacked-sm.c9ac81c58bcc.png" alt="Python Cheat Sheet" width="400" height="257">
        <div class="row">
            <form class="col-12" action="/optins/process/" method="post">
              <input type="hidden" name="slug" value="static-cheat-sheet">
              <input type="hidden" name="source_path" value="/python-mcp-client/">
              <div class="form-group">
                <input name="email" type="email" class="form-control form-control-lg" placeholder="Email Address" autocomplete="email" required autofocus>
              </div>
              <button name="submit" type="submit" class="btn btn-primary btn-lg btn-block">Send My Python Cheat&nbsp;Sheet »</button>
            </form>
          </div>
        </div>
      </div>
    </div>
  </template>
</exit-prompt>




    

    <script src="/static/frontend/components/exit-prompt.eb2a7943ea19.js" defer></script>
<script src="/static/frontend/components/code-block.2107c9a51c0a.js" defer></script>
<script src="/static/frontend/blog/components/table-of-contents.31cf386d0594.js" defer></script>
<script src="/static/frontend/blog/components/completion-status.2a257ffcb281.js" defer></script>
<script src="/static/frontend/components/share-button.6afbabfd6c49.js" defer></script>



    

    
    

    

    
    

    
    

<script>window.rp_prop_id = '58946116052';</script>
<script src="https://srv.realpython.net/tag.js" async></script>



<script id="js-context" type="application/json">{"is_completed": false, "is_bookmarked": false, "api_article_bookmark_url": "/api/v1/articles/python-mcp-client/bookmark/", "api_article_completion_status_url": "/api/v1/articles/python-mcp-client/completion_status/"}</script>






  
    <script id="dsq-count-scr" src="https://realpython.disqus.com/count.js" async></script>
    <script>
      var disqus_config = function () {
        this.page.url = 'https://realpython.com/python-mcp-client/';
        this.page.identifier = 'https://realpython.com/python-mcp-client/';
        this.callbacks.onReady = [function() {
          if (window.onDisqusReady) {
            window.onDisqusReady();
          }
        }];
      };
      var disqus_script_url = 'https://realpython.disqus.com/embed.js';
    </script>
    <script src="/static/frontend/reader/lazy-disqus.5e85d15ce13d.js" defer></script>
  


  
  
  
  <script>
    window.OneSignalDeferred = window.OneSignalDeferred || [];
    OneSignalDeferred.push(function(OneSignal) {
      OneSignal.init({
        appId: "c0081e20-a523-42bb-b0ac-04c5a9e8bf40",
        safari_web_id: "web.onesignal.auto.337e82fd-eeda-48f8-8fe5-d2f6dec774c2",
        notifyButton: {
          enable: true,
          position: "bottom-left",
          showCredit: false,
          text: {
            "tip.state.unsubscribed": "Receive a notification when we post a new tutorial",
            "tip.state.subscribed": "You're subscribed to new tutorial notifications",
            'tip.state.blocked': "You've blocked notifications",
            'message.prenotify': 'Click to subscribe to notifications',
            "message.action.subscribed": "Great, we'll let you know when the next tutorial comes out!",
            'message.action.resubscribed': "You're subscribed to notifications",
            'message.action.unsubscribed': "You won't receive notifications again",
            'dialog.main.title': 'Manage Site Notifications',
            'dialog.main.button.subscribe': 'SUBSCRIBE',
            'dialog.main.button.unsubscribe': 'UNSUBSCRIBE',
            'dialog.blocked.title': 'Unblock Notifications',
            'dialog.blocked.message': "Follow these instructions to allow notifications:"
          },
          
        },
      });
    });
    setTimeout(()=>{const n=document.createElement("script");n.src="https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js";n.async=!0;document.head.appendChild(n)}, 3000);
  </script>
  

  
    <script type="application/ld+json">{
  "@context": "http://schema.org",
  "@type": "Article",
  "headline": "Build a Python MCP Client to Test Servers From Your Terminal",
  "image": {
    "@type": "ImageObject",
    "url": "https://files.realpython.com/media/How-to-Build-a-Minimal-MCP-Client-in-Python-to-Test-Your-MCP-Servers_Watermarked.0507b57d9828.jpg",
    "width": 1920,
    "height": 1080
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://realpython.com/python-mcp-client/",
    "lastReviewed": "2025-10-16",
    "author": {
      "@type": "Person",
      "name": "Leodanis Pozo Ramos",
      "image": "https://realpython.com/cdn-cgi/image/width=862,height=862,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg",
      "url": "https://realpython.com/team/lpozoramos/",
      "affiliation": {
        "@type": "Organization",
        "@id": "https://realpython.com/#organization",
        "name": "Real Python",
        "url": "https://realpython.com",
        "logo": "https://realpython.com/static/real-python-logo-square-512.157ae6bf64ed.png"
      }
    },
    "reviewedBy": [
      {
        "@type": "Person",
        "name": "Aldren Santos",
        "image": "https://realpython.com/cdn-cgi/image/width=500,height=500,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Aldren_Santos_Real_Python.6b0861d8b841.png",
        "url": "https://realpython.com/team/asantos/",
        "affiliation": {
          "@type": "Organization",
          "@id": "https://realpython.com/#organization",
          "name": "Real Python",
          "url": "https://realpython.com",
          "logo": "https://realpython.com/static/real-python-logo-square-512.157ae6bf64ed.png"
        }
      },
      {
        "@type": "Person",
        "name": "Brenda Weleschuk",
        "image": "https://realpython.com/cdn-cgi/image/width=320,height=320,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/IMG_3324_1.50b309355fc1.jpg",
        "url": "https://realpython.com/team/bweleschuk/",
        "affiliation": {
          "@type": "Organization",
          "@id": "https://realpython.com/#organization",
          "name": "Real Python",
          "url": "https://realpython.com",
          "logo": "https://realpython.com/static/real-python-logo-square-512.157ae6bf64ed.png"
        }
      },
      {
        "@type": "Person",
        "name": "Bartosz Zaczy\u0144ski",
        "image": "https://realpython.com/cdn-cgi/image/width=1694,height=1694,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/coders_lab_2109368.259b1599fbee.jpg",
        "url": "https://realpython.com/team/bzaczynski/",
        "affiliation": {
          "@type": "Organization",
          "@id": "https://realpython.com/#organization",
          "name": "Real Python",
          "url": "https://realpython.com",
          "logo": "https://realpython.com/static/real-python-logo-square-512.157ae6bf64ed.png"
        }
      },
      {
        "@type": "Person",
        "name": "Martin Breuss",
        "image": "https://realpython.com/cdn-cgi/image/width=456,height=456,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/martin_breuss_python_square.efb2b07faf9f.jpg",
        "url": "https://realpython.com/team/mbreuss/",
        "affiliation": {
          "@type": "Organization",
          "@id": "https://realpython.com/#organization",
          "name": "Real Python",
          "url": "https://realpython.com",
          "logo": "https://realpython.com/static/real-python-logo-square-512.157ae6bf64ed.png"
        }
      }
    ]
  },
  "datePublished": "2025-11-19T14:00:00+00:00",
  "dateModified": "2025-10-16T15:39:32.766211+00:00",
  "publisher": {
    "@type": "Organization",
    "@id": "https://realpython.com/#organization",
    "name": "Real Python",
    "url": "https://realpython.com",
    "logo": {
      "@type": "ImageObject",
      "url": "https://realpython.com/static/real-python-logo-square-512.157ae6bf64ed.png",
      "width": 512,
      "height": 512
    },
    "description": "Real Python is a leading provider of online Python education and one of the largest language-specific online communities for software developers. It publishes high-quality learning resources, such as tutorials, books, and courses to an audience of millions of developers, data scientists, and machine learning engineers each month.",
    "slogan": "Become a Python Expert",
    "email": "info@realpython.com",
    "sameAs": [
      "https://github.com/realpython",
      "https://www.youtube.com/realpython",
      "https://twitter.com/realpython",
      "https://x.com/realpython",
      "https://www.linkedin.com/company/realpython-com/",
      "https://www.facebook.com/learnrealpython",
      "https://www.instagram.com/realpython",
      "https://www.tiktok.com/@realpython.com"
    ]
  },
  "author": {
    "@type": "Person",
    "name": "Leodanis Pozo Ramos",
    "image": "https://realpython.com/cdn-cgi/image/width=862,height=862,fit=crop,gravity=auto,format=auto/https://files.realpython.com/media/Perfil_final1.9f896bc212f6.jpg",
    "url": "https://realpython.com/team/lpozoramos/",
    "affiliation": {
      "@type": "Organization",
      "@id": "https://realpython.com/#organization",
      "name": "Real Python",
      "url": "https://realpython.com",
      "logo": "https://realpython.com/static/real-python-logo-square-512.157ae6bf64ed.png"
    }
  },
  "description": "Follow this Python project to build an MCP client that discovers MCP server capabilities and feeds an AI-powered chat with tool calls.",
  "hasPart": {
    "@type": "FAQPage",
    "mainEntity": [
      {
        "@type": "Question",
        "name": "How do you connect a Python MCP client to a server over stdio?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "<p>You create a client by calling <code>stdio_client()</code> with <code>StdioServerParameters</code> and wrapping the input and output streams in a <code>ClientSession</code> instance.</p>"
        }
      },
      {
        "@type": "Question",
        "name": "How do you list an MCP server\u2019s tools, prompts, and resources?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "<p>You call <code>.list_tools()</code>, <code>.list_prompts()</code>, and <code>.list_resources()</code> on a <code>ClientSession</code> instance. You can read each item&rsquo;s <code>.name</code> and <code>.description</code> attributes to get a more detailed summary.</p>"
        }
      },
      {
        "@type": "Question",
        "name": "Why does the client use an async context manager with AsyncExitStack?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "<p>The client uses an async context manager to safely manage the stdio transport and session lifetime by entering and exiting in one place. <code>AsyncExitStack</code> lets you acquire the read and write streams, attach <code>ClientSession()</code>, and guarantee cleanup even if a step fails.</p>"
        }
      }
    ]
  }
}</script>
  


    <script src="/static/frontend/optins/optins.79dabd52c2de.js" async></script>
    <script src="/static/frontend/search/autocomplete.99622433ec39.js" async></script>

    

    

    

    

    

    
<script>
  !function(f,b,e,v,n,t,s)
  {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
  n.callMethod.apply(n,arguments):n.queue.push(arguments)};
  if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
  n.queue=[];t=b.createElement(e);t.async=!0;
  t.src=v;s=b.getElementsByTagName(e)[0];
  setTimeout(function(){s.parentNode.insertBefore(t,s)}, 3000);}(window, document,'script',
  'https://connect.facebook.net/en_US/fbevents.js');
  fbq('init', '2220911568135371');
  fbq('track', 'PageView');
</script>
<noscript><img height="1" width="1" style="display:none"
  src="https://www.facebook.com/tr?id=2220911568135371&ev=PageView&noscript=1"
/></noscript>


    

    

    


  
  <div id="g_id_onload"
    data-client_id="912944204353-gv43om9f7qmj4eprm0esc45r12etunvp.apps.googleusercontent.com"
    data-login_uri="https://realpython.com/account/google/login/token/?next=/python-mcp-client/"
    data-itp_support="false">
  </div>
  <script src="//accounts.google.com/gsi/client" async></script>


  </body>
</html>
